From eee8ff92c496cebd3a8e7785da950b57cfd94717 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Fri, 25 Oct 2024 08:59:17 -0500 Subject: [PATCH 1/6] fixed period range for datavalues endpoint --- .../RunPractitionersValidationUseCase.ts | 22 +++++++------------ src/domain/usecases/common/UCDataValue.ts | 5 ++++- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/domain/usecases/RunPractitionersValidationUseCase.ts b/src/domain/usecases/RunPractitionersValidationUseCase.ts index 23075df..e1a9d7e 100644 --- a/src/domain/usecases/RunPractitionersValidationUseCase.ts +++ b/src/domain/usecases/RunPractitionersValidationUseCase.ts @@ -16,10 +16,12 @@ import { IssueRepository } from "$/domain/repositories/IssueRepository"; import { UCIssue } from "./common/UCIssue"; import { UCAnalysis } from "./common/UCAnalysis"; import i18n from "$/utils/i18n"; +import { UCDataValue } from "$/domain/usecases/common/UCDataValue"; export class RunPractitionersValidationUseCase { issueUseCase: UCIssue; analysysUseCase: UCAnalysis; + private dataValueUseCase: UCDataValue; constructor( private analysisRepository: QualityAnalysisRepository, @@ -29,6 +31,7 @@ export class RunPractitionersValidationUseCase { ) { this.analysysUseCase = new UCAnalysis(this.analysisRepository); this.issueUseCase = new UCIssue(this.issueRepository); + this.dataValueUseCase = new UCDataValue(this.dataValueRepository); } execute(options: PractitionersValidationOptions): FutureData { @@ -346,20 +349,11 @@ export class RunPractitionersValidationUseCase { } private getDataValues(qualityAnalysis: QualityAnalysis): FutureData { - const $requests = _(qualityAnalysis.countriesAnalysis) - .chunk(1) - .map(countryIds => { - return this.dataValueRepository.get({ - moduleIds: [qualityAnalysis.module.id], - countriesIds: countryIds, - period: _([qualityAnalysis.startDate, qualityAnalysis.endDate]).uniq().value(), - }); - }) - .value(); - - return Future.sequential($requests).flatMap(result => { - return Future.success(_(result).flatten().value()); - }); + return this.dataValueUseCase.get( + qualityAnalysis.countriesAnalysis, + [qualityAnalysis.module.id], + [qualityAnalysis.startDate, qualityAnalysis.endDate] + ); } private getDataElementsByDisaggregationIds( diff --git a/src/domain/usecases/common/UCDataValue.ts b/src/domain/usecases/common/UCDataValue.ts index e2b9f1c..d32b3f0 100644 --- a/src/domain/usecases/common/UCDataValue.ts +++ b/src/domain/usecases/common/UCDataValue.ts @@ -10,13 +10,16 @@ export class UCDataValue { constructor(private dataValueRepository: DataValueRepository) {} get(countriesIds: Id[], moduleIds: Id[], periods: Period[]): FutureData { + const [startDate, endDate] = periods; + if (!startDate || !endDate) throw new Error("Invalid period"); + const periodsToSearch = _.range(Number(startDate), Number(endDate) + 1); const $requests = _(countriesIds) .chunk(1) .map(countryIds => { return this.dataValueRepository.get({ moduleIds: moduleIds, countriesIds: countryIds, - period: _(periods).uniq().value(), + period: periodsToSearch.map(period => String(period)), }); }) .value(); From 7a1a8450ecd495766c055e643e51b9fdcc429110 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Wed, 29 Jan 2025 07:47:03 -0500 Subject: [PATCH 2/6] exclude global from countries in selector --- src/CompositionRoot.ts | 4 +++- src/domain/usecases/GetCountriesByIdsUseCase.ts | 11 +++++++++-- .../configuration-form/ConfigurationForm.tsx | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index 28a10c2..61bc0a1 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -88,7 +88,9 @@ type Repositories = { function getCompositionRoot(repositories: Repositories, metadata: MetadataItem) { return { - countries: { getByIds: new GetCountriesByIdsUseCase(repositories.countryRepository) }, + countries: { + getByIds: new GetCountriesByIdsUseCase(repositories.countryRepository, metadata), + }, users: { getCurrent: new GetCurrentUserUseCase(repositories.usersRepository) }, modules: { get: new GetModulesUseCase(repositories.moduleRepository), diff --git a/src/domain/usecases/GetCountriesByIdsUseCase.ts b/src/domain/usecases/GetCountriesByIdsUseCase.ts index 9575ef8..0ee5757 100644 --- a/src/domain/usecases/GetCountriesByIdsUseCase.ts +++ b/src/domain/usecases/GetCountriesByIdsUseCase.ts @@ -1,14 +1,21 @@ import { FutureData } from "$/data/api-futures"; import { Country } from "$/domain/entities/Country"; +import { MetadataItem } from "$/domain/entities/MetadataItem"; import { Id } from "$/domain/entities/Ref"; import { Future } from "$/domain/entities/generic/Future"; import { CountryRepository } from "$/domain/repositories/CountryRepository"; export class GetCountriesByIdsUseCase { - constructor(private countryRepository: CountryRepository) {} + constructor(private countryRepository: CountryRepository, private config: MetadataItem) {} execute(ids: Id[]): FutureData { if (ids.length === 0) return Future.success([]); - return this.countryRepository.getByIds(ids); + + return this.countryRepository.getByIds(ids).map(countries => { + const excludeGlobal = countries.filter( + country => country.id !== this.config.organisationUnits.global.id + ); + return excludeGlobal; + }); } } diff --git a/src/webapp/components/configuration-form/ConfigurationForm.tsx b/src/webapp/components/configuration-form/ConfigurationForm.tsx index d100e2c..f01a884 100644 --- a/src/webapp/components/configuration-form/ConfigurationForm.tsx +++ b/src/webapp/components/configuration-form/ConfigurationForm.tsx @@ -154,7 +154,7 @@ export const ConfigurationForm: React.FC = React.memo(pr onChange={onOrgUnitsChange} selected={selectedOrgUnits} levels={[1, 2, 3]} - selectableLevels={[1, 2, 3]} + selectableLevels={[2, 3]} rootIds={currentUser.countries.map(country => country.id)} withElevation={false} /> From d57eca7cf6b6556d7bd436ff15ee4b1e155fab55 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Tue, 29 Oct 2024 09:28:21 -0500 Subject: [PATCH 3/6] set configuration as initial step --- src/webapp/pages/analysis/AnalysisPage.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/webapp/pages/analysis/AnalysisPage.tsx b/src/webapp/pages/analysis/AnalysisPage.tsx index b95a7c7..f2c38b1 100644 --- a/src/webapp/pages/analysis/AnalysisPage.tsx +++ b/src/webapp/pages/analysis/AnalysisPage.tsx @@ -78,9 +78,7 @@ function StepIcon(props: { function buildStepsFromSections( analysis: QualityAnalysis, updateAnalysis: UpdateAnalysisState, - currentSection: Maybe, classes: ClassNameMap<"circle" | "pending" | "completed" | "error" | "largeIcon">, - qualityFilters: { algorithm: string; threshold: string }, onQualityFilterChange: (value: Maybe, filterAttribute: string) => void ): WizardStep[] { @@ -123,7 +121,7 @@ function buildStepsFromSections( return [ { - key: "Configuration", + key: "configuration", label: i18n.t("Configuration"), component: () => ( @@ -179,12 +177,11 @@ export const AnalysisPage: React.FC = React.memo(() => { return buildStepsFromSections( analysis, setAnalysis, - currentSection, classes, qualityFilters, onFilterChange ); - }, [analysis, setAnalysis, currentSection, classes, onFilterChange, qualityFilters]); + }, [analysis, setAnalysis, classes, onFilterChange, qualityFilters]); const onStepChange = React.useCallback( (value: string) => { @@ -208,7 +205,7 @@ export const AnalysisPage: React.FC = React.memo(() => { From 2f708f4b71a8e39eed40d439ba1394fae9dc909f Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Wed, 30 Oct 2024 08:31:03 -0500 Subject: [PATCH 4/6] allow create analysis without org. unit --- .../ValidationRuleAnalysisD2Repository.ts | 3 ++ src/domain/entities/QualityAnalysis.ts | 43 ++++++++++++++----- src/domain/entities/Settings.ts | 5 --- .../usecases/CreateQualityAnalysisUseCase.ts | 4 +- src/domain/usecases/RunValidationsUseCase.ts | 3 ++ .../usecases/SaveConfigAnalysisUseCase.ts | 18 +++++--- src/domain/usecases/common/UCDataValue.ts | 4 ++ .../configuration-form/ConfigurationForm.tsx | 19 +++++--- 8 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/data/repositories/ValidationRuleAnalysisD2Repository.ts b/src/data/repositories/ValidationRuleAnalysisD2Repository.ts index dd8e5a8..c9713d5 100644 --- a/src/data/repositories/ValidationRuleAnalysisD2Repository.ts +++ b/src/data/repositories/ValidationRuleAnalysisD2Repository.ts @@ -9,11 +9,14 @@ import { import { ValidationRule } from "$/domain/entities/ValidationRuleGroup"; import _ from "$/domain/entities/generic/Collection"; import { Future } from "$/domain/entities/generic/Future"; +import i18n from "$/utils/i18n"; export class ValidationRuleAnalysisD2Repository implements ValidationRuleAnalysisRepository { constructor(private api: D2Api) {} get(options: ValidationRuleOptions): FutureData { + if (!options.countryId) + return Future.error(new Error(i18n.t("Select at least one organisation unit"))); return apiToFuture( this.api.request({ method: "post", diff --git a/src/domain/entities/QualityAnalysis.ts b/src/domain/entities/QualityAnalysis.ts index 4c71faa..4dbf45a 100644 --- a/src/domain/entities/QualityAnalysis.ts +++ b/src/domain/entities/QualityAnalysis.ts @@ -26,12 +26,28 @@ export class QualityAnalysis extends Struct() { static build( attrs: QualityAnalysisAttrs ): Either[], QualityAnalysis> { - const qualityAnalysis = new QualityAnalysis({ - ...attrs, - name: attrs.name, - }); + const qualityAnalysis = new QualityAnalysis({ ...attrs, name: attrs.name }); + const errors: ValidationError[] = QualityAnalysis.getErrors( + qualityAnalysis, + { validateCountries: false } + ); + return errors.length === 0 ? Either.success(qualityAnalysis) : Either.error(errors); + } - const errors: ValidationError[] = [ + private static getErrors( + qualityAnalysis: QualityAnalysis, + options: { validateCountries: boolean } + ): ValidationError[] { + const countryProperty = options.validateCountries + ? [ + { + property: "countriesAnalysis" as const, + errors: validateRequired(qualityAnalysis.countriesAnalysis), + value: qualityAnalysis.countriesAnalysis, + }, + ] + : []; + return [ { property: "name" as const, errors: validateRequired(qualityAnalysis.name), @@ -57,13 +73,19 @@ export class QualityAnalysis extends Struct() { errors: validateRequired(qualityAnalysis.status), value: qualityAnalysis.status, }, + ...countryProperty, ].filter(validation => validation.errors.length > 0); + } - if (errors.length === 0) { - return Either.success(qualityAnalysis); - } else { - return Either.error(errors); - } + static updateConfiguration( + attrs: QualityAnalysisAttrs + ): Either[], QualityAnalysis> { + const qualityAnalysis = this.build(attrs).get(); + const errors: ValidationError[] = QualityAnalysis.getErrors( + qualityAnalysis, + { validateCountries: true } + ); + return errors.length === 0 ? Either.success(qualityAnalysis) : Either.error(errors); } static hasExecutedSections(qualityAnalysis: QualityAnalysis): boolean { @@ -77,6 +99,7 @@ export class QualityAnalysis extends Struct() { const formattedDate = date.toISOString().replace(/[-:T.]/g, "_"); return `${prefix} - ${formattedDate}`; } + static isModuleTwo(qualityAnalysis: QualityAnalysis): boolean { return qualityAnalysis.module.code === MODULE_2_CODE; } diff --git a/src/domain/entities/Settings.ts b/src/domain/entities/Settings.ts index d542c43..a3162f6 100644 --- a/src/domain/entities/Settings.ts +++ b/src/domain/entities/Settings.ts @@ -48,11 +48,6 @@ export class Settings extends Struct() { errors: validateRequired(settings.startDate), value: settings.startDate, }, - { - property: "countryIds" as const, - errors: validateRequired(settings.countryIds), - value: settings.countryIds, - }, ].filter(validation => validation.errors.length > 0); if (errors.length === 0) { diff --git a/src/domain/usecases/CreateQualityAnalysisUseCase.ts b/src/domain/usecases/CreateQualityAnalysisUseCase.ts index 800e4bc..95a901a 100644 --- a/src/domain/usecases/CreateQualityAnalysisUseCase.ts +++ b/src/domain/usecases/CreateQualityAnalysisUseCase.ts @@ -49,9 +49,7 @@ export class CreateQualityAnalysisUseCase { status: "In Progress", lastModification: "", countriesAnalysis: defaultSettings.countryIds, - sequential: { - value: `DQ-${sequential.value}`, - }, + sequential: { value: `DQ-${sequential.value}` }, }).match({ error: errors => { const errorMessages = getErrors(errors); diff --git a/src/domain/usecases/RunValidationsUseCase.ts b/src/domain/usecases/RunValidationsUseCase.ts index 7b8cd5c..ca873cc 100644 --- a/src/domain/usecases/RunValidationsUseCase.ts +++ b/src/domain/usecases/RunValidationsUseCase.ts @@ -14,6 +14,7 @@ import { ValidationRuleGroupRepository } from "$/domain/repositories/ValidationR import { ValidationRuleGroup } from "$/domain/entities/ValidationRuleGroup"; import { MetadataItem } from "$/domain/entities/MetadataItem"; import { CountryRepository } from "$/domain/repositories/CountryRepository"; +import i18n from "$/utils/i18n"; export class RunValidationsUseCase { private issueUseCase: UCIssue; @@ -39,6 +40,8 @@ export class RunValidationsUseCase { options.validationRuleGroupId ), }).flatMap(({ analysis, validationRuleGroup }) => { + if (analysis.countriesAnalysis.length === 0) + return Future.error(new Error(i18n.t("Select at least one organisation unit"))); const checkAllCountries = this.isGlobalInCountries(analysis.countriesAnalysis); return this.getAllCountries(checkAllCountries, analysis).flatMap(countriesIds => { return this.getValidationRuleAnalysis(analysis, countriesIds, options).flatMap( diff --git a/src/domain/usecases/SaveConfigAnalysisUseCase.ts b/src/domain/usecases/SaveConfigAnalysisUseCase.ts index a5ddb2e..bab3e51 100644 --- a/src/domain/usecases/SaveConfigAnalysisUseCase.ts +++ b/src/domain/usecases/SaveConfigAnalysisUseCase.ts @@ -2,6 +2,7 @@ import { Id } from "$/domain/entities/Ref"; import { FutureData } from "$/data/api-futures"; import { QualityAnalysis } from "$/domain/entities/QualityAnalysis"; import { QualityAnalysisRepository } from "$/domain/repositories/QualityAnalysisRepository"; +import { getErrors } from "$/domain/entities/generic/Errors"; export class SaveConfigAnalysisUseCase { constructor(private qualityAnalysisRepository: QualityAnalysisRepository) {} @@ -10,15 +11,22 @@ export class SaveConfigAnalysisUseCase { return this.getAnalysis(options.qualityAnalysis.id).flatMap(analysis => { const wasExecuted = QualityAnalysis.hasExecutedSections(analysis); const qualityAnalysisToSave = wasExecuted - ? QualityAnalysis.build({ - ...analysis, - name: options.qualityAnalysis.name, - }).get() - : QualityAnalysis.build(options.qualityAnalysis).get(); + ? QualityAnalysis.build({ ...analysis, name: options.qualityAnalysis.name }).get() + : this.updateConfigAnalysis(options.qualityAnalysis); return this.qualityAnalysisRepository.save([qualityAnalysisToSave]); }); } + private updateConfigAnalysis(analysis: QualityAnalysis): QualityAnalysis { + return QualityAnalysis.updateConfiguration(analysis).match({ + success: analysis => analysis, + error: errors => { + const errorMessage = getErrors(errors); + throw new Error(errorMessage); + }, + }); + } + private getAnalysis(id: Id): FutureData { return this.qualityAnalysisRepository.getById(id); } diff --git a/src/domain/usecases/common/UCDataValue.ts b/src/domain/usecases/common/UCDataValue.ts index d32b3f0..c88b19f 100644 --- a/src/domain/usecases/common/UCDataValue.ts +++ b/src/domain/usecases/common/UCDataValue.ts @@ -5,11 +5,15 @@ import { DataValue } from "$/domain/entities/DataValue"; import { Id, Period } from "$/domain/entities/Ref"; import { DataValueRepository } from "$/domain/repositories/DataValueRepository"; import { Future } from "$/domain/entities/generic/Future"; +import i18n from "$/utils/i18n"; export class UCDataValue { constructor(private dataValueRepository: DataValueRepository) {} get(countriesIds: Id[], moduleIds: Id[], periods: Period[]): FutureData { + if (countriesIds.length === 0) + throw new Error(i18n.t("Select at least one organisation unit")); + const [startDate, endDate] = periods; if (!startDate || !endDate) throw new Error("Invalid period"); const periodsToSearch = _.range(Number(startDate), Number(endDate) + 1); diff --git a/src/webapp/components/configuration-form/ConfigurationForm.tsx b/src/webapp/components/configuration-form/ConfigurationForm.tsx index f01a884..644c926 100644 --- a/src/webapp/components/configuration-form/ConfigurationForm.tsx +++ b/src/webapp/components/configuration-form/ConfigurationForm.tsx @@ -12,6 +12,7 @@ import _ from "$/domain/entities/generic/Collection"; import { Country } from "$/domain/entities/Country"; import styled from "styled-components"; import { getDefaultModules } from "$/data/common/D2Module"; +import { Alert } from "@material-ui/lab"; export function getIdFromCountriesPaths(paths: string[]): string[] { return _(paths) @@ -60,11 +61,7 @@ export const ConfigurationForm: React.FC = React.memo(pr }, [countries]); const modules = getDefaultModules(metadata); - - const moduleItems = modules.map(module => ({ - value: module.id, - text: module.name, - })); + const moduleItems = modules.map(module => ({ value: module.id, text: module.name })); const onFormSubmit = React.useCallback( (e: React.FormEvent) => { @@ -111,6 +108,14 @@ export const ConfigurationForm: React.FC = React.memo(pr return (
+ {selectedOrgUnits.length === 0 && ( + + + {i18n.t("Select at least one organisation unit")} + + + )} + ` const ActionsContainer = styled.div` text-align: right; `; + +const AlertContainer = styled.div` + margin-block: 1em; +`; From aee076786201eb6aa9cf3d5bd09e55eed2c857b0 Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Mon, 4 Nov 2024 10:12:01 -0500 Subject: [PATCH 5/6] improve error if country is not selected --- i18n/en.pot | 7 +++- i18n/es.po | 5 ++- src/domain/entities/QualityAnalysis.ts | 5 ++- src/domain/entities/generic/Errors.ts | 3 +- src/domain/entities/generic/validations.ts | 11 ++++- .../usecases/GetCountriesByIdsUseCase.ts | 7 +--- .../configuration-form/ConfigurationForm.tsx | 4 +- src/webapp/pages/analysis/AnalysisPage.tsx | 40 +++++++++++++++++-- .../analysis/steps/ConfigurationStep.tsx | 9 ++++- 9 files changed, 71 insertions(+), 20 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 7579a3a..823f027 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-05-16T07:04:50.457Z\n" -"PO-Revision-Date: 2024-05-16T07:04:50.458Z\n" +"POT-Creation-Date: 2024-10-30T13:32:55.750Z\n" +"PO-Revision-Date: 2024-10-30T13:32:55.750Z\n" msgid "ID" msgstr "" @@ -56,6 +56,9 @@ msgstr "" msgid "No" msgstr "" +msgid "Select at least one organisation unit" +msgstr "" + msgid "Cannot be blank: {{fieldName}}" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index dbe9420..1427865 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-05-16T07:04:50.457Z\n" +"POT-Creation-Date: 2024-10-30T13:32:55.750Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -56,6 +56,9 @@ msgstr "" msgid "No" msgstr "" +msgid "Select at least one organisation unit" +msgstr "" + msgid "Cannot be blank: {{fieldName}}" msgstr "" diff --git a/src/domain/entities/QualityAnalysis.ts b/src/domain/entities/QualityAnalysis.ts index 4dbf45a..5e68e29 100644 --- a/src/domain/entities/QualityAnalysis.ts +++ b/src/domain/entities/QualityAnalysis.ts @@ -42,7 +42,10 @@ export class QualityAnalysis extends Struct() { ? [ { property: "countriesAnalysis" as const, - errors: validateRequired(qualityAnalysis.countriesAnalysis), + errors: validateRequired( + qualityAnalysis.countriesAnalysis, + "country_validation" + ), value: qualityAnalysis.countriesAnalysis, }, ] diff --git a/src/domain/entities/generic/Errors.ts b/src/domain/entities/generic/Errors.ts index 2119dc7..c22250d 100644 --- a/src/domain/entities/generic/Errors.ts +++ b/src/domain/entities/generic/Errors.ts @@ -1,8 +1,9 @@ import i18n from "$/utils/i18n"; -export type ValidationErrorKey = "field_cannot_be_blank"; +export type ValidationErrorKey = "field_cannot_be_blank" | "country_validation"; export const validationErrorMessages: Record string> = { + country_validation: () => i18n.t("Select at least one organisation unit"), field_cannot_be_blank: (fieldName: string) => i18n.t(`Cannot be blank: {{fieldName}}`, { fieldName: fieldName, nsSeparator: false }), }; diff --git a/src/domain/entities/generic/validations.ts b/src/domain/entities/generic/validations.ts index 2a47f85..4a19512 100644 --- a/src/domain/entities/generic/validations.ts +++ b/src/domain/entities/generic/validations.ts @@ -1,7 +1,14 @@ import { ValidationErrorKey } from "./Errors"; -export function validateRequired(value: any): ValidationErrorKey[] { +export function validateRequired( + value: any, + errorCode: ValidationErrorKey = "field_cannot_be_blank" +): ValidationErrorKey[] { const isBlank = !value || (value.length !== undefined && value.length === 0); - return isBlank ? ["field_cannot_be_blank"] : []; + return isBlank ? setErrorCode(errorCode) : []; +} + +function setErrorCode(errorCode?: ValidationErrorKey): ValidationErrorKey[] { + return errorCode ? [errorCode] : ["field_cannot_be_blank"]; } diff --git a/src/domain/usecases/GetCountriesByIdsUseCase.ts b/src/domain/usecases/GetCountriesByIdsUseCase.ts index 0ee5757..cb3fd8c 100644 --- a/src/domain/usecases/GetCountriesByIdsUseCase.ts +++ b/src/domain/usecases/GetCountriesByIdsUseCase.ts @@ -11,11 +11,6 @@ export class GetCountriesByIdsUseCase { execute(ids: Id[]): FutureData { if (ids.length === 0) return Future.success([]); - return this.countryRepository.getByIds(ids).map(countries => { - const excludeGlobal = countries.filter( - country => country.id !== this.config.organisationUnits.global.id - ); - return excludeGlobal; - }); + return this.countryRepository.getByIds(ids); } } diff --git a/src/webapp/components/configuration-form/ConfigurationForm.tsx b/src/webapp/components/configuration-form/ConfigurationForm.tsx index 644c926..dc84c59 100644 --- a/src/webapp/components/configuration-form/ConfigurationForm.tsx +++ b/src/webapp/components/configuration-form/ConfigurationForm.tsx @@ -45,7 +45,7 @@ export function useCountries(props: UseCountriesProps) { } export const ConfigurationForm: React.FC = React.memo(props => { - const { initialData, onSave } = props; + const { initialData, onSave, updateCountry } = props; const { api, currentUser, metadata } = useAppContext(); const { countries } = useCountries({ ids: initialData.countriesAnalysis }); const [formData, setFormData] = React.useState(() => { @@ -101,6 +101,7 @@ export const ConfigurationForm: React.FC = React.memo(pr const onOrgUnitsChange = (value: Id[]) => { setSelectedOrgUnits(value); + updateCountry(value.length > 0); }; const disableSave = QualityAnalysis.hasExecutedSections(formData); @@ -177,6 +178,7 @@ export const ConfigurationForm: React.FC = React.memo(pr type ConfigurationFormProps = { initialData: QualityAnalysis; onSave: (data: QualityAnalysis) => void; + updateCountry: React.Dispatch>; }; type UseCountriesProps = { ids: Id[] }; diff --git a/src/webapp/pages/analysis/AnalysisPage.tsx b/src/webapp/pages/analysis/AnalysisPage.tsx index f2c38b1..88ba518 100644 --- a/src/webapp/pages/analysis/AnalysisPage.tsx +++ b/src/webapp/pages/analysis/AnalysisPage.tsx @@ -19,6 +19,7 @@ import _ from "$/domain/entities/generic/Collection"; import { QualityAnalysisSection } from "$/domain/entities/QualityAnalysisSection"; import { Maybe } from "$/utils/ts-utils"; import { SummaryStep } from "./SummaryStep"; +import { getErrors } from "$/domain/entities/generic/Errors"; const defaultOutlierParams = { algorithm: "Z_SCORE", threshold: "3" }; @@ -80,7 +81,8 @@ function buildStepsFromSections( updateAnalysis: UpdateAnalysisState, classes: ClassNameMap<"circle" | "pending" | "completed" | "error" | "largeIcon">, qualityFilters: { algorithm: string; threshold: string }, - onQualityFilterChange: (value: Maybe, filterAttribute: string) => void + onQualityFilterChange: (value: Maybe, filterAttribute: string) => void, + setCountrySelected: React.Dispatch> ): WizardStep[] { const sectionSteps = _(analysis.sections) .map((section): Maybe => { @@ -104,6 +106,7 @@ function buildStepsFromSections( ), key: section.name.toLowerCase(), label: section.name, + props: { analysis: analysis }, component: () => ( ( - + ), completed: false, icon: , @@ -133,6 +141,7 @@ function buildStepsFromSections( { icon: , key: "Summary", + props: { analysis }, label: i18n.t("Summary"), component: () => , completed: false, @@ -162,6 +171,7 @@ export const AnalysisPage: React.FC = React.memo(() => { }; const { analysis, setAnalysis, isLoading, error } = useAnalysisById(id); + const [countrySelected, setCountrySelected] = React.useState(false); useEffect(() => { if (isLoading) loading.show(); @@ -179,9 +189,10 @@ export const AnalysisPage: React.FC = React.memo(() => { setAnalysis, classes, qualityFilters, - onFilterChange + onFilterChange, + setCountrySelected ); - }, [analysis, setAnalysis, classes, onFilterChange, qualityFilters]); + }, [analysis, setAnalysis, classes, onFilterChange, qualityFilters, setCountrySelected]); const onStepChange = React.useCallback( (value: string) => { @@ -192,6 +203,25 @@ export const AnalysisPage: React.FC = React.memo(() => { [analysis] ); + const validateAnalysis = React.useCallback( + async (currentStep: WizardStep) => { + if (!currentStep.props) return Promise.resolve([]); + const currentAnalysis = currentStep.props as { analysis: QualityAnalysis }; + return QualityAnalysis.updateConfiguration(currentAnalysis.analysis).match({ + success: () => { + return Promise.resolve([]); + }, + error: errors => { + const errorMessage = countrySelected + ? i18n.t("You must save the Analysis configuration before running any step") + : getErrors(errors); + return Promise.resolve([errorMessage]); + }, + }); + }, + [countrySelected] + ); + if (!analysis) return null; const firstSectionName = _(analysis.sections).first()?.name.toLowerCase(); @@ -208,6 +238,8 @@ export const AnalysisPage: React.FC = React.memo(() => { initialStepKey="configuration" steps={analysisSteps} onStepChange={onStepChange} + onStepChangeRequest={validateAnalysis} + useSnackFeedback /> ); diff --git a/src/webapp/pages/analysis/steps/ConfigurationStep.tsx b/src/webapp/pages/analysis/steps/ConfigurationStep.tsx index 268de89..713178a 100644 --- a/src/webapp/pages/analysis/steps/ConfigurationStep.tsx +++ b/src/webapp/pages/analysis/steps/ConfigurationStep.tsx @@ -11,12 +11,13 @@ import { UpdateAnalysisState } from "$/webapp/pages/analysis/AnalysisPage"; export type ConfigurationStepProps = { analysis: QualityAnalysis; updateAnalysis: UpdateAnalysisState; + updateCountry: React.Dispatch>; }; const noop = () => {}; export const ConfigurationStep: React.FC = React.memo(props => { - const { analysis, updateAnalysis } = props; + const { analysis, updateAnalysis, updateCountry } = props; const { saveQualityAnalysis } = useAnalysisMethods({ onSuccess: noop, onRemove: noop, @@ -37,7 +38,11 @@ export const ConfigurationStep: React.FC = React.memo(pr {i18n.t("Configuration")} - + ); }); From aa6f70f512011583c100d50c5277e73c7a80e66b Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Mon, 4 Nov 2024 10:13:51 -0500 Subject: [PATCH 6/6] update i18n --- i18n/en.pot | 7 +++++-- i18n/es.po | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 823f027..842a42c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-10-30T13:32:55.750Z\n" -"PO-Revision-Date: 2024-10-30T13:32:55.750Z\n" +"POT-Creation-Date: 2024-11-04T15:12:23.322Z\n" +"PO-Revision-Date: 2024-11-04T15:12:23.322Z\n" msgid "ID" msgstr "" @@ -202,6 +202,9 @@ msgstr "" msgid "Summary" msgstr "" +msgid "You must save the Analysis configuration before running any step" +msgstr "" + msgid "Algorithm" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index 1427865..db94fec 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-10-30T13:32:55.750Z\n" +"POT-Creation-Date: 2024-11-04T15:12:23.322Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -202,6 +202,9 @@ msgstr "" msgid "Summary" msgstr "" +msgid "You must save the Analysis configuration before running any step" +msgstr "" + msgid "Algorithm" msgstr ""