From b297bbb284ff746e45603e1029a79d7ebd95149a Mon Sep 17 00:00:00 2001 From: Eduardo Peredo Rivero Date: Tue, 10 Dec 2024 09:13:04 -0500 Subject: [PATCH] refactor and delete unused files --- src/CompositionRoot.ts | 4 - .../CoreCompetencyD2Repository.ts | 64 +++++++++------ .../{D2ApiConfig.ts => D2ApiMetadata.ts} | 49 ++++++------ .../repositories/DataElementD2Repository.ts | 65 +++++++++------ src/data/repositories/DataSetD2Api.ts | 2 +- src/data/repositories/DataSetD2Repository.ts | 79 +++++++++++-------- .../repositories/IndicatorD2Repository.ts | 2 +- src/data/repositories/ProjectD2Repository.ts | 8 +- src/data/utils.ts | 24 ++++++ src/domain/entities/DataElement.ts | 7 +- src/domain/entities/DataSet.ts | 26 ++---- src/domain/entities/Indicator.ts | 4 +- src/domain/entities/Settings.ts | 43 ---------- .../usecases/GetCoreCompetencyUseCase.ts | 11 --- src/domain/usecases/SaveDataSetUseCase.ts | 9 ++- .../usecases/ValidateDataSetNameUseCase.ts | 2 +- src/domain/usecases/common/DataSetUtils.ts | 2 +- src/utils/uid.ts | 3 + .../dataset-wizard/DataSetWizard.tsx | 2 +- .../dataset-wizard/FilterIndicators.tsx | 58 +++++++------- .../dataset-wizard/IndicatorsDataSet.tsx | 53 +++++-------- .../register-dataset/RegisterDataSetPage.tsx | 6 +- 22 files changed, 262 insertions(+), 261 deletions(-) rename src/data/repositories/{D2ApiConfig.ts => D2ApiMetadata.ts} (79%) delete mode 100644 src/domain/entities/Settings.ts delete mode 100644 src/domain/usecases/GetCoreCompetencyUseCase.ts diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index 9b0d55f7..2fb1c363 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -20,7 +20,6 @@ import { LogRepository } from "$/domain/repositories/LogRepository"; import { OrgUnitRepository } from "$/domain/repositories/OrgUnitRepository"; import { ProjectRepository } from "$/domain/repositories/ProjectRepository"; import { GetAllProjectsUseCase } from "$/domain/usecases/GetAllProjectsUseCase"; -import { GetCoreCompetencyUseCase } from "$/domain/usecases/GetCoreCompetencyUseCase"; import { GetDataSetSettingsUseCase } from "$/domain/usecases/GetDataSetSettingsUseCase"; import { GetDataSetsByIdsUseCase } from "$/domain/usecases/GetDataSetsByIdsUseCase"; import { GetDataSetsUseCase } from "$/domain/usecases/GetDataSetsUseCase"; @@ -95,9 +94,6 @@ function getCompositionRoot(repositories: Repositories) { orgUnits: { getByIds: new GetOrgUnitsByIdsUseCase(repositories.orgUnitRepository), }, - coreCompetencies: { - getAll: new GetCoreCompetencyUseCase(repositories.coreCompetencyRepository), - }, indicators: { get: new GetIndicatorsUseCase(repositories.indicatorRepository), }, diff --git a/src/data/repositories/CoreCompetencyD2Repository.ts b/src/data/repositories/CoreCompetencyD2Repository.ts index a92401d8..05d58a6b 100644 --- a/src/data/repositories/CoreCompetencyD2Repository.ts +++ b/src/data/repositories/CoreCompetencyD2Repository.ts @@ -3,8 +3,9 @@ import { CoreCompetency } from "$/domain/entities/DataSet"; import { CoreCompetencyRepository } from "$/domain/repositories/CoreCompetencyRepository"; import { Future, FutureData } from "$/domain/entities/generic/Future"; import { apiToFuture } from "$/data/api-futures"; -import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiConfig"; +import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiMetadata"; import i18n from "$/utils/i18n"; +import { Id } from "$/domain/entities/Ref"; export class CoreCompetencyD2Repository implements CoreCompetencyRepository { private d2ApiConfig: D2ApiConfig; @@ -16,35 +17,48 @@ export class CoreCompetencyD2Repository implements CoreCompetencyRepository { getAll(): FutureData { return this.getConfig().flatMap(config => { const coreCompetencyId = config.dataElementGroupSets.coreCompetency.id; - return apiToFuture( - this.api.models.dataElementGroupSets.get({ - fields: { - id: true, - displayName: true, - dataElementGroups: { id: true, code: true, displayName: true }, - }, - filter: { id: { eq: coreCompetencyId } }, - }) - ).flatMap(d2Response => { - const coreCompetencyGroup = d2Response.objects[0]; - return coreCompetencyGroup - ? Future.success( - coreCompetencyGroup.dataElementGroups.map( - (d2DataElementGroup): CoreCompetency => ({ - id: d2DataElementGroup.id, - code: d2DataElementGroup.code, - name: d2DataElementGroup.displayName, - }) - ) - ) - : Future.error( - new Error(i18n.t(`Core competency group not found: ${coreCompetencyId}`)) - ); + return this.getDataElementGroupSetById(coreCompetencyId).flatMap(d2GroupSet => { + return this.mapToCoreCompetencies(d2GroupSet.dataElementGroups); }); }); } + private getDataElementGroupSetById(id: Id): FutureData { + return apiToFuture( + this.api.models.dataElementGroupSets.get({ + fields: { + id: true, + displayName: true, + dataElementGroups: { id: true, code: true, displayName: true }, + }, + filter: { id: { eq: id } }, + }) + ).flatMap(d2Response => { + const d2DataElementGroupSet = d2Response.objects[0]; + return d2DataElementGroupSet + ? Future.success(d2DataElementGroupSet) + : Future.error(new Error(i18n.t(`dataElementGroupSet not found: ${id}`))); + }); + } + + private mapToCoreCompetencies( + dataElementGroups: D2DataElementGroup[] + ): FutureData { + return Future.success( + dataElementGroups.map( + (d2DataElementGroup): CoreCompetency => ({ + id: d2DataElementGroup.id, + code: d2DataElementGroup.code, + name: d2DataElementGroup.displayName, + }) + ) + ); + } + private getConfig(): FutureData { return this.d2ApiConfig.get(); } } + +type D2DataElementGroupSet = { id: Id; dataElementGroups: D2DataElementGroup[] }; +type D2DataElementGroup = { id: Id; displayName: string; code: string }; diff --git a/src/data/repositories/D2ApiConfig.ts b/src/data/repositories/D2ApiMetadata.ts similarity index 79% rename from src/data/repositories/D2ApiConfig.ts rename to src/data/repositories/D2ApiMetadata.ts index 72ff8fa2..549c72ce 100644 --- a/src/data/repositories/D2ApiConfig.ts +++ b/src/data/repositories/D2ApiMetadata.ts @@ -65,60 +65,63 @@ export class D2ApiConfig { private getMetadata(): FutureData { return apiToFuture(this.api.metadata.get(metadataFields)).map(d2Response => { + const getOrThrowMetadata = (metadataKey: keyof typeof metadataFields, code: string) => + getOrThrow(d2Response[metadataKey], code); + return { attributes: this.buildAttributes(d2Response.attributes), categories: { - project: getOrThrow(d2Response.categories, metadataCodes.categories.project), + project: getOrThrowMetadata("categories", metadataCodes.categories.project), }, dataElementGroupSets: { - coreCompetency: getOrThrow( - d2Response.dataElementGroupSets, + coreCompetency: getOrThrowMetadata( + "dataElementGroupSets", metadataCodes.dataElementGroupSets.coreCompetency ), - theme: getOrThrow( - d2Response.dataElementGroupSets, + theme: getOrThrowMetadata( + "dataElementGroupSets", metadataCodes.dataElementGroupSets.theme ), - status: getOrThrow( - d2Response.dataElementGroupSets, + status: getOrThrowMetadata( + "dataElementGroupSets", metadataCodes.dataElementGroupSets.status ), }, dataElementGroups: { - localIndicator: getOrThrow( - d2Response.dataElementGroups, + localIndicator: getOrThrowMetadata( + "dataElementGroups", metadataCodes.dataElementGroups.localIndicator ), - donorIndicator: getOrThrow( - d2Response.dataElementGroups, + donorIndicator: getOrThrowMetadata( + "dataElementGroups", metadataCodes.dataElementGroups.donorIndicator ), - coreIndicator: getOrThrow( - d2Response.dataElementGroups, + coreIndicator: getOrThrowMetadata( + "dataElementGroups", metadataCodes.dataElementGroups.coreIndicator ), }, indicatorGroups: { - coreIndicator: getOrThrow( - d2Response.indicatorGroups, + coreIndicator: getOrThrowMetadata( + "indicatorGroups", metadataCodes.indicatorGroup.coreIndicator ), - donorIndicator: getOrThrow( - d2Response.indicatorGroups, + donorIndicator: getOrThrowMetadata( + "indicatorGroups", metadataCodes.indicatorGroup.donorIndicator ), - localIndicator: getOrThrow( - d2Response.indicatorGroups, + localIndicator: getOrThrowMetadata( + "indicatorGroups", metadataCodes.indicatorGroup.localIndicator ), }, indicatorGroupSets: { - theme: getOrThrow( - d2Response.indicatorGroupSets, + theme: getOrThrowMetadata( + "indicatorGroupSets", metadataCodes.indicatorGroupSets.theme ), - status: getOrThrow( - d2Response.indicatorGroupSets, + status: getOrThrowMetadata( + "indicatorGroupSets", metadataCodes.indicatorGroupSets.status ), }, diff --git a/src/data/repositories/DataElementD2Repository.ts b/src/data/repositories/DataElementD2Repository.ts index e261a2fc..43ffce40 100644 --- a/src/data/repositories/DataElementD2Repository.ts +++ b/src/data/repositories/DataElementD2Repository.ts @@ -12,34 +12,49 @@ export class DataElementD2Repository implements DataElementRepository { if (identifiables.length === 0) return Future.success([]); return chunkRequest(identifiables, values => { - return apiToFuture( - this.api.models.dataElements.get({ - fields: { - id: true, - displayName: true, - code: true, - categoryCombo: { id: true, displayName: true }, - }, - filter: { identifiable: { in: values } }, - paging: false, - }) - ).map(response => { - return response.objects; - }); + return this.getByIdentifiables(values); }).map(response => { return response.map((d2DataElement): DataElement => { - return { - code: d2DataElement.code, - id: d2DataElement.id, - name: d2DataElement.displayName, - disaggregation: d2DataElement.categoryCombo - ? { - id: d2DataElement.categoryCombo.id, - name: d2DataElement.categoryCombo.displayName, - } - : undefined, - }; + return this.buildDataElement(d2DataElement); }); }); } + + private buildDataElement(d2DataElement: D2ApiDataElement): DataElement { + return { + code: d2DataElement.code, + id: d2DataElement.id, + name: d2DataElement.displayName, + disaggregation: d2DataElement.categoryCombo + ? { + id: d2DataElement.categoryCombo.id, + name: d2DataElement.categoryCombo.displayName, + } + : undefined, + }; + } + + private getByIdentifiables(identifiables: string[]) { + return apiToFuture( + this.api.models.dataElements.get({ + fields: { + id: true, + displayName: true, + code: true, + categoryCombo: { id: true, displayName: true }, + }, + filter: { identifiable: { in: identifiables } }, + paging: false, + }) + ).map(response => { + return response.objects; + }); + } } + +type D2ApiDataElement = { + code: string; + id: string; + displayName: string; + categoryCombo: { id: string; displayName: string }; +}; diff --git a/src/data/repositories/DataSetD2Api.ts b/src/data/repositories/DataSetD2Api.ts index 8787f93a..c72278d6 100644 --- a/src/data/repositories/DataSetD2Api.ts +++ b/src/data/repositories/DataSetD2Api.ts @@ -18,7 +18,7 @@ import { Permission } from "$/domain/entities/Permission"; import _ from "$/domain/entities/generic/Collection"; import { Project } from "$/domain/entities/Project"; import { D2ApiCategoryOption } from "$/data/repositories/D2ApiCategoryOption"; -import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiConfig"; +import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiMetadata"; import { Pager } from "@eyeseetea/d2-api/api"; export class DataSetD2Api { diff --git a/src/data/repositories/DataSetD2Repository.ts b/src/data/repositories/DataSetD2Repository.ts index ecbb9cc1..88c17548 100644 --- a/src/data/repositories/DataSetD2Repository.ts +++ b/src/data/repositories/DataSetD2Repository.ts @@ -1,4 +1,4 @@ -import { D2AttributeValue } from "@eyeseetea/d2-api/2.36"; +import { D2AttributeValue, MetadataPick } from "@eyeseetea/d2-api/2.36"; import { D2Api, MetadataResponse } from "$/types/d2-api"; import { apiToFuture } from "$/data/api-futures"; @@ -14,9 +14,9 @@ import { getUid } from "$/utils/uid"; import _ from "$/domain/entities/generic/Collection"; import { DataSetD2Api, dataSetFieldsWithOrgUnits } from "$/data/repositories/DataSetD2Api"; import { Maybe } from "$/utils/ts-utils"; -import { chunkRequest } from "$/data/utils"; -import { D2Config } from "$/data/repositories/D2ApiConfig"; -import { Indicator } from "$/domain/entities/Indicator"; +import { chunkRequest, runMetadata } from "$/data/utils"; +import { D2Config } from "$/data/repositories/D2ApiMetadata"; +import { Indicator, IndicatorAttrs } from "$/domain/entities/Indicator"; import { Id, Ref } from "$/domain/entities/Ref"; import { DataSetToSave } from "$/domain/entities/DataSetToSave"; @@ -106,37 +106,21 @@ export class DataSetD2Repository implements DataSetRepository { const $requests = chunkRequest(ids, dataSetIds => { return apiToFuture( this.api.models.dataSets.get({ - fields: { $owner: true }, + fields: ownerFields, filter: { id: { in: dataSetIds } }, paging: false, }) ).flatMap(d2Response => { - const dataSetsToSave = dataSetIds.map(dataSetId => { - const existingDataSet = d2Response.objects.find(ds => ds.id === dataSetId); - const dataSet = dataSets.find(dataSet => dataSet.id === dataSetId); - if (!dataSet) { - throw Error(`Cannot find dataSet: ${dataSetId}`); - } - - const existingAttributes = existingDataSet?.attributeValues; - - const result = { - ...(existingDataSet || {}), - ...this.buildD2DataSet(dataSet, existingAttributes, config.attributes), - }; - - const { sharing: _, ...rest } = result; - return rest; - }); - - return apiToFuture( + const dataSetsToSave = this.getD2DataSetsToSave( + dataSetIds, + d2Response.objects, + dataSets, + config + ); + + return runMetadata( this.api.metadata.post({ dataSets: dataSetsToSave }) - ).flatMap(response => { - const allErrors = this.extractErrorsFromResponse(response); - - if (allErrors.length > 0) - return Future.error(new Error(allErrors.join("\n"))); - + ).flatMap(() => { return this.saveAllSections(dataSetsToSave, dataSets).map(() => []); }); }); @@ -146,6 +130,31 @@ export class DataSetD2Repository implements DataSetRepository { }); } + private getD2DataSetsToSave( + dataSetIds: string[], + d2DataSets: D2DataSetOwner[], + dataSets: DataSetToSave[], + config: D2Config + ) { + return dataSetIds.map(dataSetId => { + const existingDataSet = d2DataSets.find(ds => ds.id === dataSetId); + const dataSet = dataSets.find(dataSet => dataSet.id === dataSetId); + if (!dataSet) { + throw Error(`Cannot find dataSet: ${dataSetId}`); + } + + const existingAttributes = existingDataSet?.attributeValues; + + const result = { + ...(existingDataSet || {}), + ...this.buildD2DataSet(dataSet, existingAttributes, config.attributes), + }; + + const { sharing: _, ...rest } = result; + return rest; + }); + } + delete(ids: string[]): FutureData { if (ids.length === 0) return Future.success(undefined); @@ -389,4 +398,12 @@ type D2DataSetSection = { indicators: Ref[]; }; -const indicatorTypeLabel = { outcomes: "Outcomes", outputs: "Outputs" }; +const indicatorTypeLabel: Record = { + outcomes: "Outcomes", + outputs: "Outputs", +}; + +const ownerFields = { $owner: true }; +type D2DataSetOwner = MetadataPick<{ + dataSets: { fields: typeof ownerFields }; +}>["dataSets"][number]; diff --git a/src/data/repositories/IndicatorD2Repository.ts b/src/data/repositories/IndicatorD2Repository.ts index 76bbe4c8..0728260a 100644 --- a/src/data/repositories/IndicatorD2Repository.ts +++ b/src/data/repositories/IndicatorD2Repository.ts @@ -1,5 +1,5 @@ import { apiToFuture } from "$/data/api-futures"; -import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiConfig"; +import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiMetadata"; import { Future, FutureData } from "$/domain/entities/generic/Future"; import { Indicator } from "$/domain/entities/Indicator"; import { IndicatorRepository } from "$/domain/repositories/IndicatorRepository"; diff --git a/src/data/repositories/ProjectD2Repository.ts b/src/data/repositories/ProjectD2Repository.ts index 0b92d3d2..dcfe6889 100644 --- a/src/data/repositories/ProjectD2Repository.ts +++ b/src/data/repositories/ProjectD2Repository.ts @@ -13,7 +13,7 @@ import { D2CategoryOptionWithDates, } from "$/data/repositories/D2ApiCategoryOption"; import { Future, FutureData } from "$/domain/entities/generic/Future"; -import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiConfig"; +import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiMetadata"; import { Maybe } from "$/utils/ts-utils"; export class ProjectD2Repository implements ProjectRepository { @@ -27,8 +27,8 @@ export class ProjectD2Repository implements ProjectRepository { getList(): FutureData { return this.getCategories().flatMap(categories => { - return this.getCategoryOptionsByCode(categories.project.code).map(d2Response => { - return this.getProjectsWithDates(d2Response.objects); + return this.getCategoryOptionsByCode(categories.project.code).map(categoryOptions => { + return this.getProjectsWithDates(categoryOptions); }); }); } @@ -51,7 +51,7 @@ export class ProjectD2Repository implements ProjectRepository { order: "displayName:asc", paging: false, }) - ); + ).map(response => response.objects); } private getProjectsWithDates(categoryOptions: D2CategoryOptionWithDates[]): Project[] { diff --git a/src/data/utils.ts b/src/data/utils.ts index cbc35434..9edcd645 100644 --- a/src/data/utils.ts +++ b/src/data/utils.ts @@ -2,6 +2,9 @@ import { FutureData } from "$/domain/entities/generic/Future"; import { Id } from "$/domain/entities/Ref"; import { Future } from "$/domain/entities/generic/Future"; import _ from "$/domain/entities/generic/Collection"; +import { MetadataResponse } from "@eyeseetea/d2-api/api"; +import { CancelableResponse } from "@eyeseetea/d2-api"; +import { apiToFuture } from "$/data/api-futures"; export function chunkRequest( ids: Id[], @@ -17,3 +20,24 @@ export function chunkRequest( .value() ); } + +export function getErrorFromResponse(res: MetadataResponse): string { + console.debug(JSON.stringify(res, null, 4)); + + const errorsMessages = res.typeReports + .flatMap(typeReport => typeReport.objectReports) + .flatMap(objectReport => objectReport.errorReports) + .flatMap(errorReport => errorReport.message); + + return _(errorsMessages) + .filter(error => error.length > 0) + .join("\n"); +} + +export function runMetadata(d2Response: CancelableResponse): FutureData { + return apiToFuture(d2Response).flatMap(res => + res.status !== "OK" + ? Future.error(new Error(getErrorFromResponse(res))) + : Future.success(undefined) + ); +} diff --git a/src/domain/entities/DataElement.ts b/src/domain/entities/DataElement.ts index bf3bfc8c..1918466a 100644 --- a/src/domain/entities/DataElement.ts +++ b/src/domain/entities/DataElement.ts @@ -1,4 +1,9 @@ import { Id, NamedRef } from "$/domain/entities/Ref"; import { Maybe } from "$/utils/ts-utils"; -export type DataElement = { id: Id; name: string; code: string; disaggregation: Maybe }; +export type DataElement = { + id: Id; + name: string; + code: string; + disaggregation: Maybe; +}; diff --git a/src/domain/entities/DataSet.ts b/src/domain/entities/DataSet.ts index 4092788e..b4cbffb9 100644 --- a/src/domain/entities/DataSet.ts +++ b/src/domain/entities/DataSet.ts @@ -41,9 +41,8 @@ export type CoreCompetency = { id: Id; name: string; code: string }; export type DataSetList = Pick; export class DataSet extends Struct() { - validate(): Either[], DataSet> { - const allErrors = this.getValidationErrors(); - return allErrors.length === 0 ? Either.success(this) : Either.error(allErrors); + validate(): ValidationError[] { + return this.getValidationErrors(); } get shortName(): string { @@ -81,25 +80,16 @@ export class DataSet extends Struct() { return this._update({ indicators }); } - validateIndicatorsStep(): Either[], DataSet> { - return this.indicators.length > 0 - ? Either.success(this) - : Either.error([ + validateIndicatorsStep(): ValidationError[] { + return this.indicators.length === 0 + ? [ { property: "indicators" as const, errors: ["indicators_required"], value: this.indicators, }, - ]); - } - - static buildOrgUnitsFromPaths(paths: string[]): OrgUnit[] { - const orgUnits = paths.map(path => ({ - id: _(path.split("/")).last() || "", - name: path, - path: path.split("/").slice(1), - })); - return orgUnits; + ] + : []; } static buildAccess(permissions: Permissions): string { @@ -114,7 +104,7 @@ export class DataSet extends Struct() { private getValidationErrors(): ValidationError[] { const setupErrors = this.buildSetupErrors(); - const indicatorsErrors = this.validateIndicatorsStep().value.error || []; + const indicatorsErrors = this.validateIndicatorsStep(); return [...setupErrors, ...indicatorsErrors]; } diff --git a/src/domain/entities/Indicator.ts b/src/domain/entities/Indicator.ts index a0a9a279..49f08e45 100644 --- a/src/domain/entities/Indicator.ts +++ b/src/domain/entities/Indicator.ts @@ -12,7 +12,7 @@ export type IndicatorAttrs = { theme: string; status: string; type: "outputs" | "outcomes"; - scope: "core" | "local" | "donor"; + scope: ScopeType; group: string; disaggregation: Maybe; coreCompetency: CoreCompetency; @@ -21,6 +21,8 @@ export type IndicatorAttrs = { relatedDataElements: DataElement[]; }; +export type ScopeType = "core" | "local" | "donor"; + export class Indicator extends Struct() { setRelatedDataElements( dataElements: DataElement[], diff --git a/src/domain/entities/Settings.ts b/src/domain/entities/Settings.ts deleted file mode 100644 index 34861a7d..00000000 --- a/src/domain/entities/Settings.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Struct } from "$/domain/entities/generic/Struct"; - -export type AppSettingsAttrs = { - attributeGroupId: string; - categoryComboId: string; - categoryProjectsId: string; - createdByDataSetConfigurationAttributeId: string; - dataElementGroupGlobalIndicatorMandatoryId: string; - dataElementGroupOutputId: string; - dataElementGroupSetCoreCompetencyId: string; - dataElementGroupSetOriginId: string; - dataElementGroupSetStatusId: string; - dataElementGroupSetThemeId: string; - dataPeriodIntervalDatesAttributeId: string; - dataPeriodOutcomeDatesAttributeId: string; - dataPeriodOutputDatesAttributeId: string; - exclusionRuleCoreUserGroupId: string; - expiryDays: number; - hideInDataSetAppAttributeId: string; - indicatorGroupGlobalIndicatorMandatoryId: string; - indicatorGroupSetOriginId: string; - indicatorGroupSetStatusId: string; - indicatorGroupSetThemeId: string; - organisationUnitLevelForCountriesId: string; - outcomeEndDate: { - day: number; - month: number; - }; - outcomeLastYearEndDate: { - units: string; - value: number; - }; - outputEndDate: { - day: number; - month: number; - }; - outputLastYearEndDate: { - units: string; - value: number; - }; -}; - -export class AppSettings extends Struct() {} diff --git a/src/domain/usecases/GetCoreCompetencyUseCase.ts b/src/domain/usecases/GetCoreCompetencyUseCase.ts deleted file mode 100644 index ae644fd2..00000000 --- a/src/domain/usecases/GetCoreCompetencyUseCase.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CoreCompetency } from "$/domain/entities/DataSet"; -import { FutureData } from "$/domain/entities/generic/Future"; -import { CoreCompetencyRepository } from "$/domain/repositories/CoreCompetencyRepository"; - -export class GetCoreCompetencyUseCase { - constructor(private coreCompetencyRepository: CoreCompetencyRepository) {} - - public execute(): FutureData { - return this.coreCompetencyRepository.getAll(); - } -} diff --git a/src/domain/usecases/SaveDataSetUseCase.ts b/src/domain/usecases/SaveDataSetUseCase.ts index 1b82612a..b323fb99 100644 --- a/src/domain/usecases/SaveDataSetUseCase.ts +++ b/src/domain/usecases/SaveDataSetUseCase.ts @@ -23,8 +23,8 @@ export class SaveDataSetUseCase { execute(dataSet: DataSet): FutureData { const result = dataSet.validate(); - if (result.isError()) { - const errors = getErrors(result.value.error); + if (result.length > 0) { + const errors = getErrors(result); return Future.error(new Error(errors.join("\n"))); } @@ -51,7 +51,10 @@ export class SaveDataSetUseCase { } private validateDataSetName(dataSet: DataSet): FutureData { - return this.dataSetUtils.dataSetNameExists({ name: dataSet.name, dataSetId: dataSet.id }); + return this.dataSetUtils.isDataSetNameDuplicate({ + name: dataSet.name, + dataSetId: dataSet.id, + }); } private getDataElementsRelatedFromIndicators(dataSet: DataSet): FutureData { diff --git a/src/domain/usecases/ValidateDataSetNameUseCase.ts b/src/domain/usecases/ValidateDataSetNameUseCase.ts index 9aa7786e..f5c30b87 100644 --- a/src/domain/usecases/ValidateDataSetNameUseCase.ts +++ b/src/domain/usecases/ValidateDataSetNameUseCase.ts @@ -9,6 +9,6 @@ export class ValidateDataSetNameUseCase { } execute(options: ValidateDataSetNameOptions): FutureData { - return this.dataSetUtils.dataSetNameExists(options); + return this.dataSetUtils.isDataSetNameDuplicate(options); } } diff --git a/src/domain/usecases/common/DataSetUtils.ts b/src/domain/usecases/common/DataSetUtils.ts index e9fd58f6..9ef1421a 100644 --- a/src/domain/usecases/common/DataSetUtils.ts +++ b/src/domain/usecases/common/DataSetUtils.ts @@ -5,7 +5,7 @@ import { DataSetRepository } from "$/domain/repositories/DataSetRepository"; export class DataSetUtils { constructor(private dataSetRepository: DataSetRepository) {} - dataSetNameExists(options: ValidateDataSetNameOptions): FutureData { + isDataSetNameDuplicate(options: ValidateDataSetNameOptions): FutureData { return this.dataSetRepository.getByName(options.name).map(dataSets => { return dataSets.some( dataSet => diff --git a/src/utils/uid.ts b/src/utils/uid.ts index 9032ebb7..673111d6 100644 --- a/src/utils/uid.ts +++ b/src/utils/uid.ts @@ -45,6 +45,9 @@ export function generateUid(): string { return randomChars; } +// @ts-ignore +window.generateUid = generateUid; + const fullUidRegex = /^[a-zA-Z]{1}[a-zA-Z0-9]{10}$/; export function isValidUid(code: string | undefined | null): boolean { diff --git a/src/webapp/components/dataset-wizard/DataSetWizard.tsx b/src/webapp/components/dataset-wizard/DataSetWizard.tsx index 628d89b1..0aea7fcd 100644 --- a/src/webapp/components/dataset-wizard/DataSetWizard.tsx +++ b/src/webapp/components/dataset-wizard/DataSetWizard.tsx @@ -134,7 +134,7 @@ export function useValidateDataSetWizard(props: { return Promise.resolve([]); } }, - [dataSet, validationInProgressOrError] + [dataSet, validationInProgressOrError, validationStatus] ); return { validateSteps }; diff --git a/src/webapp/components/dataset-wizard/FilterIndicators.tsx b/src/webapp/components/dataset-wizard/FilterIndicators.tsx index 8a2296ab..b5b00893 100644 --- a/src/webapp/components/dataset-wizard/FilterIndicators.tsx +++ b/src/webapp/components/dataset-wizard/FilterIndicators.tsx @@ -17,7 +17,7 @@ export type FilterIndicatorsProps = { groups: string[]; group: string; onClose: () => void; - onFilterChange: (scope: ChipItem | ChipItem[], type: FilterType) => void; + onFilterChange: (scope: ChipItem[], type: FilterType) => void; scopes: string[]; scopeValue: string; showCloseButton?: boolean; @@ -83,14 +83,17 @@ export const FilterIndicators = React.memo((props: FilterIndicatorsProps) => { ({ text: s, value: s }))} + items={scopes.map(scope => ({ text: scope, value: scope }))} label={i18n.t("Scope")} onChange={value => onFilterChange(value, "scope")} value={scopeValue} /> ({ text: c.name, value: c.id }))} + items={coreCompetencies.map(coreCompetency => ({ + text: coreCompetency.name, + value: coreCompetency.id, + }))} label={i18n.t("Core competencies")} onChange={value => onFilterChange(value, "core")} value={coreValue} @@ -110,7 +113,7 @@ export const FilterIndicators = React.memo((props: FilterIndicatorsProps) => { className="dropdown" items={themes.map(t => ({ text: t, value: t }))} onChange={value => - onFilterChange({ text: value ?? "", value: value ?? "" }, "theme") + onFilterChange([{ text: value ?? "", value: value ?? "" }], "theme") } label={i18n.t("Theme")} value={theme} @@ -122,7 +125,7 @@ export const FilterIndicators = React.memo((props: FilterIndicatorsProps) => { className="dropdown" items={groups.map(g => ({ text: g, value: g }))} onChange={value => - onFilterChange({ text: value ?? "", value: value ?? "" }, "group") + onFilterChange([{ text: value ?? "", value: value ?? "" }], "group") } label={i18n.t("Group")} value={group} @@ -135,7 +138,7 @@ export const FilterIndicators = React.memo((props: FilterIndicatorsProps) => { export type ChipFilterProps = { items: ChipItem[]; label: string; - onChange: (item: ChipItem | ChipItem[]) => void; + onChange: (item: ChipItem[]) => void; value: string | string[]; mode?: "single" | "multiple"; }; @@ -146,36 +149,31 @@ export const ChipFilter = React.memo((props: ChipFilterProps) => { const { items, label, onChange, value, mode = "single" } = props; const handleChipClick = (itemValue: string) => { - if (mode === "single") { - const currentItem = items.find(item => item.value === itemValue); - if (currentItem) onChange(currentItem); - } else if (mode === "multiple") { - const selectedValues = Array.isArray(value) ? value : []; - const currentValues = selectedValues.filter(v => v !== itemValue); - const chipItems = _(currentValues) - .compactMap(value => { - return items.find(item => item.value === value); - }) - .value(); - if (selectedValues.includes(itemValue)) { - onChange(chipItems); - } else { - const itemToRemove = items.find(item => item.value === itemValue); - if (itemToRemove) { - onChange(chipItems.concat([itemToRemove])); - } - } - } - }; + const selectedValues = Array.isArray(value) ? value : []; + const currentItem = items.find(item => item.value === itemValue); + + if (!currentItem) return; - const isSelected = (itemValue: string) => { if (mode === "single") { - return itemValue === value; + onChange([currentItem]); } else { - return Array.isArray(value) && value.includes(itemValue); + const isSelected = selectedValues.includes(itemValue); + const updatedItems = isSelected + ? selectedValues + .filter( + value => value !== itemValue && items.find(item => item.value === value) + ) + .map(value => ({ text: value, value })) + : [...selectedValues, itemValue] + .filter(value => items.find(item => item.value === value)) + .map(value => ({ text: value, value })); + + onChange(updatedItems); } }; + const isSelected = (itemValue: string) => value.includes(itemValue); + return ( diff --git a/src/webapp/components/dataset-wizard/IndicatorsDataSet.tsx b/src/webapp/components/dataset-wizard/IndicatorsDataSet.tsx index b3773eae..984845f0 100644 --- a/src/webapp/components/dataset-wizard/IndicatorsDataSet.tsx +++ b/src/webapp/components/dataset-wizard/IndicatorsDataSet.tsx @@ -98,39 +98,26 @@ export const IndicatorsDataSet = React.memo((props: IndicatorsDataSetProps) => { setShowFilterModal(true); }, []); - const updateFilter = React.useCallback( - (value: ChipItem | ChipItem[], filterType: FilterType) => { - const isArray = Array.isArray(value); - switch (filterType) { - case "scope": - if (!isArray) { - setScope(value.value); - } - break; - case "core": - if (isArray) { - setCore(value.map(v => v.value)); - } - break; - case "outputType": - if (!isArray) { - setType(value.value); - } - break; - case "theme": - if (!isArray) { - setTheme(value.value); - } - break; - case "group": - if (!isArray) { - setGroup(value.value); - } - break; - } - }, - [] - ); + const updateFilter = React.useCallback((value: ChipItem[], filterType: FilterType) => { + const singleItemValue = value[0]?.value || ""; + switch (filterType) { + case "scope": + setScope(singleItemValue); + break; + case "core": + setCore(value.map(v => v.value)); + break; + case "outputType": + setType(singleItemValue); + break; + case "theme": + setTheme(singleItemValue); + break; + case "group": + setGroup(singleItemValue); + break; + } + }, []); const activeFilters = React.useMemo(() => { const competencies = _(selectedCompetencies) diff --git a/src/webapp/pages/register-dataset/RegisterDataSetPage.tsx b/src/webapp/pages/register-dataset/RegisterDataSetPage.tsx index 1577d683..969f4fc9 100644 --- a/src/webapp/pages/register-dataset/RegisterDataSetPage.tsx +++ b/src/webapp/pages/register-dataset/RegisterDataSetPage.tsx @@ -7,7 +7,7 @@ import { DataSetWizard } from "$/webapp/components/dataset-wizard/DataSetWizard" import { useAppContext } from "$/webapp/contexts/app-context"; import { useLoading, useSnackbar } from "@eyeseetea/d2-ui-components"; import { Project } from "$/domain/entities/Project"; -import { getUid } from "$/utils/uid"; +import { generateUid } from "$/utils/uid"; import { component } from "$/utils/react"; import i18n from "$/utils/i18n"; import { DataSetSettings } from "$/domain/entities/DataSetSettings"; @@ -46,9 +46,7 @@ export function useGetDataSetSettings(props: { id: Id }) { const { compositionRoot } = useAppContext(); const snackbar = useSnackbar(); const [status, setStatus] = React.useState("idle"); - const [dataSet, updateDataSet] = React.useState( - DataSet.initial(getUid(new Date().getTime().toString())) - ); + const [dataSet, updateDataSet] = React.useState(DataSet.initial(generateUid())); const [dataSetSettings, setDataSetSettings] = React.useState(); React.useEffect(() => {