From 344ffee79acc8bec38cf930a7d19eee24af29448 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 27 Sep 2024 12:51:05 +0200 Subject: [PATCH] feat: adjust species-sheet config structure --- .../core/gn_synthese/synthese_config.py | 78 ------------------- backend/geonature/utils/config_schema.py | 31 +------- config/default_config.toml.example | 15 +++- .../src/app/syntheseModule/synthese.module.ts | 3 +- .../taxon-sheet/indicator/indicator.ts | 35 +++++---- .../tab-profile/tab-profile.component.ts | 53 +++++++++---- .../taxon-sheet/taxon-sheet.component.ts | 58 ++++++++++---- .../taxon-sheet/taxon-sheet.route.service.ts | 72 ++++++----------- 8 files changed, 136 insertions(+), 209 deletions(-) diff --git a/backend/geonature/core/gn_synthese/synthese_config.py b/backend/geonature/core/gn_synthese/synthese_config.py index c862a2ce1c..a3129d8d1b 100644 --- a/backend/geonature/core/gn_synthese/synthese_config.py +++ b/backend/geonature/core/gn_synthese/synthese_config.py @@ -100,81 +100,3 @@ {"prop": "dataset_name", "name": "JDD", "max_width": 200}, {"prop": "observers", "name": "observateur", "max_width": 200}, ] - - -class DefaultProfile: - ENABLED = True - ## DEFAULT PROFILE INDICATORS - LIST_INDICATORS = [ - { - "name": "observation(s) valide(s)", - "matIcon": "search", - "field": "count_valid_data", - "type": "number", - }, - { - "name": "Première observation", - "matIcon": "schedule", - "field": "first_valid_data", - "type": "date", - }, - { - "name": "Dernière observation", - "matIcon": "search", - "field": "last_valid_data", - "type": "date", - }, - { - "name": "Plage d'altitude(s)", - "matIcon": "terrain", - "field": ["altitude_min", "altitude_max"], - "unit": "m", - "type": "number", - }, - ] - - -class DefaultTaxonomy: - ENABLED = True - - -class DefaultGeographicOverview: - pass - - -class DefaultSpeciesSheet: - ## DEFAULT SPECIES SHEET INDICATORS - LIST_INDICATORS = [ - { - "name": "observation(s)", - "matIcon": "search", - "field": "observation_count", - "type": "number", - }, - { - "name": "observateur(s)", - "matIcon": "people", - "field": "observer_count", - "type": "number", - }, - { - "name": "commune(s)", - "matIcon": "location_on", - "field": "area_count", - "type": "number", - }, - { - "name": "Plage d'altitude(s)", - "matIcon": "terrain", - "unit": "m", - "type": "number", - "field": ["altitude_min", "altitude_max"], - }, - { - "name": "Plage d'observation(s)", - "matIcon": "date_range", - "type": "date", - "field": ["date_min", "date_max"], - "separator": "-", - }, - ] diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 76676465b8..021f98da7b 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -18,14 +18,7 @@ ) from marshmallow.validate import OneOf, Regexp, Email, Length -from geonature.core.gn_synthese.synthese_config import ( - DEFAULT_EXPORT_COLUMNS, - DEFAULT_LIST_COLUMN, - DefaultGeographicOverview, - DefaultProfile, - DefaultTaxonomy, - DefaultSpeciesSheet, -) +from geonature.core.gn_synthese.synthese_config import DEFAULT_EXPORT_COLUMNS, DEFAULT_LIST_COLUMN from geonature.utils.env import GEONATURE_VERSION, BACKEND_DIR, ROOT_DIR from geonature.utils.module import iter_modules_dist, get_module_config from geonature.utils.utilsmails import clean_recipients @@ -278,29 +271,11 @@ class ExportObservationSchema(Schema): geojson_local_field = fields.String(load_default="geojson_local") -class SpeciesSheetTaxonomy(Schema): - ENABLED = fields.Boolean(load_default=DefaultTaxonomy.ENABLED) - - -class SpeciesSheetProfile(Schema): - ENABLED = fields.Boolean(load_default=DefaultProfile.ENABLED) - LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultProfile.LIST_INDICATORS) - - -class SpeciesSheetGeographicOverview(Schema): - pass - - class SpeciesSheet(Schema): # -------------------------------------------------------------------- # SYNTHESE - SPECIES_SHEET - LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultSpeciesSheet.LIST_INDICATORS) - - GEOGRAPHIC_OVERVIEW = fields.Dict( - load_default=SpeciesSheetGeographicOverview().load({}) - ) # rename - PROFILE = fields.Nested(SpeciesSheetProfile, load_default=SpeciesSheetProfile().load({})) - TAXONOMY = fields.Nested(SpeciesSheetTaxonomy, load_default=SpeciesSheetTaxonomy().load({})) + ENABLE_PROFILE = fields.Boolean(load_default=True) + ENABLE_TAXONOMY = fields.Boolean(load_default=True) class Synthese(Schema): diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 05fa5a6a83..36f75ac331 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -307,9 +307,9 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Vues d'export personnalisées EXPORT_OBSERVATIONS_CUSTOM_VIEWS = [ { - label = "format personnalisé", + label = "format personnalisé", view_name = "schema_name.view_name" - } + } ] # Noms des colonnes obligatoires de la vue ``gn_synthese.v_metadata_for_export`` EXPORT_METADATA_ID_DATASET_COL = "jdd_id" @@ -441,6 +441,13 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Seulement les données de présence cd_nomenclature_observation_status = ['Pr'] + [SYNTHESE.SPECIES_SHEET] + # Options dédiées à la fiche espèce + #Permet d'activer ou non la section "Profile" de la fiche espèce + ENABLE_PROFILE = True + # Permet d'activer ou non la section "Taxonomy" de la fiche espèce + ENABLE_TAXONOMY = True + # Gestion des demandes d'inscription [ACCOUNT_MANAGEMENT] # Activer l'affichage du lien vers le formulaire d'inscription @@ -601,7 +608,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" [[AUTHENTICATION.PROVIDERS]] module="pypnusershub.auth.providers.default.DefaultConfiguration" id_provider="local_provider" - + [[AUTHENTICATION.PROVIDERS]] module="pypnusershub.auth.providers.openid_provider.OpenIDConnectProvider" id_provider = "google" @@ -609,4 +616,4 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" label = "Google" ISSUER = "https://accounts.google.com/" CLIENT_ID = "ID" # API key - CLIENT_SECRET = "SECRET" # API Secret \ No newline at end of file + CLIENT_SECRET = "SECRET" # API Secret diff --git a/frontend/src/app/syntheseModule/synthese.module.ts b/frontend/src/app/syntheseModule/synthese.module.ts index 993b8f5cdd..a8310b63f7 100644 --- a/frontend/src/app/syntheseModule/synthese.module.ts +++ b/frontend/src/app/syntheseModule/synthese.module.ts @@ -19,7 +19,6 @@ import { TaxonSheetComponent } from './taxon-sheet/taxon-sheet.component'; import { RouteService, ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES, - ROUTE_MANDATORY, } from './taxon-sheet/taxon-sheet.route.service'; const routes: Routes = [ @@ -32,7 +31,7 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: ROUTE_MANDATORY.path, + redirectTo: ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES[0].path, pathMatch: 'prefix', }, ...ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.map((tab) => { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts index 728a454f50..441fb02c80 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts @@ -1,16 +1,17 @@ -type IndicatorRawType = 'number' | 'string' | 'date'; -export interface IndicatorRaw { +export interface Indicator { name: string; matIcon: string; - field: string | Array; - unit?: string; - type: IndicatorRawType; + value: string | null; } -export interface Indicator { +type IndicatorRawType = 'number' | 'string' | 'date'; +export interface IndicatorDescription { name: string; matIcon: string; - value: string | null; + field: string | Array; + unit?: string; + separator?: string; + type: IndicatorRawType; } type Stats = Record; @@ -18,7 +19,7 @@ type Stats = Record; const DEFAULT_VALUE = '-'; const DEFAULT_SEPARATOR = '-'; -function getValue(field: string, indicatorConfig: IndicatorRaw, stats?: Stats) { +function getValue(field: string, indicatorConfig: IndicatorDescription, stats?: Stats) { if (stats && stats[field]) { let valueAsString = ''; switch (indicatorConfig.type) { @@ -37,24 +38,24 @@ function getValue(field: string, indicatorConfig: IndicatorRaw, stats?: Stats) { return DEFAULT_VALUE; } -export function computeIndicatorFromConfig( - indicatorConfig: IndicatorRaw, +export function computeIndicatorFromDecsription( + indicatorDescription: IndicatorDescription, stats?: Stats ): Indicator { let value = DEFAULT_VALUE; if (stats) { - if (Array.isArray(indicatorConfig.field)) { - const separator = indicatorConfig['separator'] ?? DEFAULT_SEPARATOR; - value = indicatorConfig.field - .map((field) => getValue(field, indicatorConfig, stats)) + if (Array.isArray(indicatorDescription.field)) { + const separator = indicatorDescription.separator ?? DEFAULT_SEPARATOR; + value = indicatorDescription.field + .map((field) => getValue(field, indicatorDescription, stats)) .join(' ' + separator + ' '); } else { - value = getValue(indicatorConfig.field, indicatorConfig, stats); + value = getValue(indicatorDescription.field, indicatorDescription, stats); } } return { - name: indicatorConfig.name, - matIcon: indicatorConfig.matIcon, + name: indicatorDescription.name, + matIcon: indicatorDescription.matIcon, value: value, }; } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts index a0897604bf..0e0f51d908 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts @@ -1,14 +1,45 @@ import { Component, OnInit } from '@angular/core'; import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { CommonModule } from '@angular/common'; -import { ConfigService } from '@geonature/services/config.service'; import { DataFormService, Profile } from '@geonature_common/form/data-form.service'; import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; import { CommonService } from '@geonature_common/service/common.service'; -import { computeIndicatorFromConfig, Indicator, IndicatorRaw } from '../indicator/indicator'; +import { + computeIndicatorFromDecsription, + Indicator, + IndicatorDescription, +} from '../indicator/indicator'; import { IndicatorComponent } from '../indicator/indicator.component'; import { TaxonSheetService } from '../taxon-sheet.service'; +const INDICATORS: Array = [ + { + name: 'observation(s) valide(s)', + matIcon: 'search', + field: 'count_valid_data', + type: 'number', + }, + { + name: 'Première observation', + matIcon: 'schedule', + field: 'first_valid_data', + type: 'date', + }, + { + name: 'Dernière observation', + matIcon: 'search', + field: 'last_valid_data', + type: 'date', + }, + { + name: "Plage d'altitude(s)", + matIcon: 'terrain', + field: ['altitude_min', 'altitude_max'], + unit: 'm', + type: 'number', + }, +]; + @Component({ standalone: true, selector: 'tab-profile', @@ -21,7 +52,6 @@ export class TabProfileComponent implements OnInit { _profile: Profile | null; constructor( - private _config: ConfigService, private _ds: DataFormService, private _commonService: CommonService, private _tss: TaxonSheetService @@ -54,19 +84,8 @@ export class TabProfileComponent implements OnInit { set profile(profile: Profile | null) { this._profile = profile; - - if ( - this._config && - this._config['SYNTHESE'] && - this._config['SYNTHESE']['SPECIES_SHEET'] && - this._config['SYNTHESE']['SPECIES_SHEET']['PROFILE'] - ) { - this.indicators = this._config['SYNTHESE']['SPECIES_SHEET']['PROFILE']['LIST_INDICATORS'].map( - (indicatorConfig: IndicatorRaw) => - computeIndicatorFromConfig(indicatorConfig, profile?.properties) - ); - } else { - this.indicators = []; - } + this.indicators = INDICATORS.map((indicatorRaw: IndicatorDescription) => + computeIndicatorFromDecsription(indicatorRaw, profile?.properties) + ); } } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts index d2fa308d7e..a17055c723 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts @@ -6,17 +6,55 @@ import { RouterLinkActive, RouterOutlet, } from '@angular/router'; -import { ConfigService } from '@geonature/services/config.service'; import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { InfosComponent } from './infos/infos.component'; import { LayoutComponent } from './layout/layout.component'; -import { computeIndicatorFromConfig, Indicator, IndicatorRaw } from './indicator/indicator'; +import { + computeIndicatorFromDecsription, + Indicator, + IndicatorDescription, +} from './indicator/indicator'; import { IndicatorComponent } from './indicator/indicator.component'; import { CommonModule } from '@angular/common'; import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; import { TaxonSheetService } from './taxon-sheet.service'; import { RouteService } from './taxon-sheet.route.service'; +const INDICATORS: Array = [ + { + name: 'observation(s)', + matIcon: 'search', + field: 'observation_count', + type: 'number', + }, + { + name: 'observateur(s)', + matIcon: 'people', + field: 'observer_count', + type: 'number', + }, + { + name: 'commune(s)', + matIcon: 'location_on', + field: 'area_count', + type: 'number', + }, + { + name: "Plage d'altitude(s)", + matIcon: 'terrain', + unit: 'm', + type: 'number', + field: ['altitude_min', 'altitude_max'], + }, + { + name: "Plage d'observation(s)", + matIcon: 'date_range', + type: 'date', + field: ['date_min', 'date_max'], + separator: '-', + }, +]; + @Component({ standalone: true, selector: 'pnx-taxon-sheet', @@ -44,7 +82,6 @@ export class TaxonSheetComponent implements OnInit { private _route: ActivatedRoute, private _tss: TaxonSheetService, private _syntheseDataService: SyntheseDataService, - private _config: ConfigService, private _routes: RouteService ) { this.TAB_LINKS = this._routes.TAB_LINKS; @@ -62,18 +99,9 @@ export class TaxonSheetComponent implements OnInit { } setIndicators(stats: any) { - if ( - this._config && - this._config['SYNTHESE'] && - this._config['SYNTHESE']['SPECIES_SHEET'] && - this._config['SYNTHESE']['SPECIES_SHEET']['LIST_INDICATORS'] - ) { - this.indicators = this._config['SYNTHESE']['SPECIES_SHEET']['LIST_INDICATORS'].map( - (indicatorConfig: IndicatorRaw) => computeIndicatorFromConfig(indicatorConfig, stats) - ); - } else { - this.indicators = []; - } + this.indicators = INDICATORS.map((indicatorConfig: IndicatorDescription) => + computeIndicatorFromDecsription(indicatorConfig, stats) + ); } goToPath(path: string) { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts index c584288d27..9e6a110ad8 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts @@ -1,10 +1,8 @@ import { Injectable } from '@angular/core'; import { - CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, - ActivatedRoute, CanActivateChild, } from '@angular/router'; import { ConfigService } from '@geonature/services/config.service'; @@ -16,37 +14,29 @@ import { TabTaxonomyComponent } from './tab-taxonomy/tab-taxonomy.component'; interface Tab { label: string; path: string; - configEntry: string; + configEnabledField?: string; component: any; } -const ROUTE_GEOGRAPHIC_OVERVIEW: Tab = { - label: 'Synthèse Géographique', - path: 'geographic_overview', - configEntry: 'GEOGRAPHIC_OVERVIEW', - component: TabGeographicOverviewComponent, -}; - -export const ROUTE_MANDATORY = ROUTE_GEOGRAPHIC_OVERVIEW; - -const ROUTE_TAXONOMY: Tab = { - label: 'Taxonomie', - path: 'taxonomy', - configEntry: 'TAXONOMY', - component: TabTaxonomyComponent, -}; - -const ROUTE_PROFILE: Tab = { - label: 'Profil', - path: 'profile', - configEntry: 'PROFILE', - component: TabProfileComponent, -}; - export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ - ROUTE_GEOGRAPHIC_OVERVIEW, - ROUTE_TAXONOMY, - ROUTE_PROFILE, + { + label: 'Synthèse Géographique', + path: 'geographic_overview', + component: TabGeographicOverviewComponent, + configEnabledField: null, // make it always available ! + }, + { + label: 'Taxonomie', + path: 'taxonomy', + configEnabledField: 'TAXONOMY', + component: TabTaxonomyComponent, + }, + { + label: 'Profil', + path: 'profile', + configEnabledField: 'ENABLE_PROFILE', + component: TabProfileComponent, + }, ]; @Injectable({ @@ -54,20 +44,15 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ }) export class RouteService implements CanActivateChild { readonly TAB_LINKS = []; - constructor( private _config: ConfigService, private _router: Router ) { - this.TAB_LINKS.push(ROUTE_MANDATORY); - if (this._config && this._config['SYNTHESE'] && this._config['SYNTHESE']['SPECIES_SHEET']) { + if (this._config['SYNTHESE']?.['SPECIES_SHEET']) { const config = this._config['SYNTHESE']['SPECIES_SHEET']; - if (config['TAXONOMY'] && config['TAXONOMY']['ENABLED']) { - this.TAB_LINKS.push(ROUTE_TAXONOMY); - } - if (config['PROFILE'] && config['PROFILE']['ENABLED']) { - this.TAB_LINKS.push(ROUTE_PROFILE); - } + this.TAB_LINKS = ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.filter( + (tab) => !tab.configEnabledField || config[tab.configEnabledField] + ); } } @@ -76,18 +61,9 @@ export class RouteService implements CanActivateChild { state: RouterStateSnapshot ): Observable | Promise | boolean { const targetedPath = childRoute.routeConfig.path; - if (ROUTE_MANDATORY.path == targetedPath) { + if (this.TAB_LINKS.map((tab) => tab.path).includes(targetedPath)) { return true; } - const targetedTab = ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.find( - (tab) => tab.path === targetedPath - ); - if (this._config && this._config['SYNTHESE'] && this._config['SYNTHESE']['SPECIES_SHEET']) { - const config = this._config['SYNTHESE']['SPECIES_SHEET']; - if (config[targetedTab.configEntry] && config[targetedTab.configEntry]['ENABLED']) { - return true; - } - } this._router.navigate(['/404'], { skipLocationChange: true }); return false;