From 177f197d55b4ccd80caa64382ab32feba2205d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20B?= <9062561+jrmyb@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:33:46 +0100 Subject: [PATCH] feat(core): export data based on dot notation structure definition (#686) * export data based on dot notation structure * fix type * fix type * fix lint * use set instead of reduceRight+merge on getMappingRecordValues (exportData) * delete coms --- .../src/domain/attribute/attributeDomain.ts | 2 +- .../src/domain/export/exportDomain.spec.ts | 112 ++++++++++++++++++ apps/core/src/domain/export/exportDomain.ts | 8 +- 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/apps/core/src/domain/attribute/attributeDomain.ts b/apps/core/src/domain/attribute/attributeDomain.ts index 018dd1ee3..ad4d830dc 100644 --- a/apps/core/src/domain/attribute/attributeDomain.ts +++ b/apps/core/src/domain/attribute/attributeDomain.ts @@ -118,7 +118,7 @@ export default function ({ doesCompute(attrData): boolean { const availableActions = actionsListDomain.getAvailableActions(); - return (attrData.actions_list?.[ActionsListEvents.GET_VALUE] || []).some( + return (attrData.actions_list?.[ActionsListEvents.GET_VALUE] ?? []).some( action => availableActions.find(availableAction => availableAction.id === action.id)?.compute ); }, diff --git a/apps/core/src/domain/export/exportDomain.spec.ts b/apps/core/src/domain/export/exportDomain.spec.ts index 86905bdda..c1dcfc423 100644 --- a/apps/core/src/domain/export/exportDomain.spec.ts +++ b/apps/core/src/domain/export/exportDomain.spec.ts @@ -138,5 +138,117 @@ describe('exportDomain', () => { } ]); }); + + it('should export data with a structure based on dot notation keys', async () => { + const jsonMapping = JSON.stringify({ + simple: 'bikes.bikes_label', + 'link.link': 'bikes.bikes_activity.activities_label', + 'link.preview': 'bikes.bikes_visual.files_previews.medium', + 'no_value.no_value.no_value': 'bikes.no_value', + shop_label: 'shops.shops_label' + }); + + const mockAttributeDomain: Mockify = { + getAttributeProperties: jest.fn() + }; + + const mockUtils: Mockify = { + isLinkAttribute: jest.fn() + }; + + const attributeProperties = { + bikes_label: {format: AttributeFormats.TEXT}, + bikes_activity: {linked_library: 'activities'}, + activities_label: {format: AttributeFormats.TEXT}, + bikes_visual: {linked_library: 'files'}, + files_previews: {format: AttributeFormats.EXTENDED}, + no_value: {format: AttributeFormats.TEXT, linked_library: false}, + shops_label: {format: AttributeFormats.TEXT} + }; + + when(mockUtils.isLinkAttribute) + .calledWith({id: 'bikes_visual', ...attributeProperties.bikes_visual}) + .mockReturnValue(true); + when(mockUtils.isLinkAttribute) + .calledWith({id: 'bikes_activity', ...attributeProperties.bikes_activity}) + .mockReturnValue(true); + + Object.entries(attributeProperties).forEach(([id, returnValue]) => + when(mockAttributeDomain.getAttributeProperties) + .calledWith({id, ctx: mockCtx}) + .mockReturnValue({id, ...returnValue}) + ); + + const mockRecordDomain: Mockify = { + getRecordFieldValue: jest.fn() + }; + + const fieldValues = [ + { + library: 'bikes', + recordId: 'bikeId', + attributeId: 'bikes_label', + returnValue: [{payload: 'bikeLabel'}] + }, + { + library: 'bikes', + recordId: 'bikeId', + attributeId: 'bikes_activity', + returnValue: [{payload: {id: 'activityId'}}] + }, + { + library: 'activities', + recordId: 'activityId', + attributeId: 'activities_label', + returnValue: [{payload: 'activityLabel'}] + }, + { + library: 'bikes', + recordId: 'bikeId', + attributeId: 'bikes_visual', + returnValue: [{payload: {id: 'fileId'}}] + }, + { + library: 'files', + recordId: 'fileId', + attributeId: 'files_previews', + returnValue: [{payload: JSON.stringify({medium: '/path/to/preview'})}] + }, + {library: 'bikes', recordId: 'bikeId', attributeId: 'no_value', returnValue: []}, + { + library: 'shops', + recordId: 'shopId', + attributeId: 'shops_label', + returnValue: [{payload: 'shopLabel'}] + } + ]; + + fieldValues.forEach(({library, recordId, attributeId, returnValue}) => + when(mockRecordDomain.getRecordFieldValue) + .calledWith({library, record: {id: recordId}, attributeId, ctx: mockCtx}) + .mockReturnValue(returnValue) + ); + + const domain = exportDomain({ + ...depsBase, + 'core.domain.record': mockRecordDomain as IRecordDomain, + 'core.domain.attribute': mockAttributeDomain as IAttributeDomain, + 'core.utils': mockUtils as IUtils + }); + + const data = await domain.exportData(jsonMapping, [{bikes: 'bikeId', shops: 'shopId'}], mockCtx); + + expect(data).toEqual([ + { + link: { + link: 'activityLabel', + preview: '/path/to/preview' + }, + no_value: {no_value: {no_value: ''}}, + simple: 'bikeLabel', + shop_label: 'shopLabel' + } + ]); + }); }); }); diff --git a/apps/core/src/domain/export/exportDomain.ts b/apps/core/src/domain/export/exportDomain.ts index 4a8a54e2d..45709d88f 100644 --- a/apps/core/src/domain/export/exportDomain.ts +++ b/apps/core/src/domain/export/exportDomain.ts @@ -10,7 +10,7 @@ import {IRecordDomain} from 'domain/record/recordDomain'; import {ITasksManagerDomain} from 'domain/tasksManager/tasksManagerDomain'; import ExcelJS from 'exceljs'; import {i18n} from 'i18next'; -import {pick} from 'lodash'; +import {pick, set} from 'lodash'; import path from 'path'; import {IUtils} from 'utils/utils'; import {v4 as uuidv4} from 'uuid'; @@ -38,7 +38,7 @@ export interface IExportDomain { jsonMapping: string, elements: Array<{[libraryId: string]: string}>, ctx: IQueryInfos - ): Promise>; + ): Promise>; } export interface IExportDomainDeps { @@ -205,7 +205,7 @@ export default function ({ jsonMapping: string, recordsToExport: Array<{[libraryId: string]: string}>, ctx: IQueryInfos - ): Promise> { + ): Promise> { const mapping = JSON.parse(jsonMapping) as Record; const mappingKeysByLibrary = _getMappingKeysByLibrary(mapping); @@ -213,7 +213,7 @@ export default function ({ keys.reduce(async (acc, key) => { const nestedAttributes = mapping[key].split('.').slice(1); // first element is the library id, we delete it const value = await _getInDepthValue(libraryId, recordId, nestedAttributes, ctx); - return {...(await acc), [key]: value}; + return set(await acc, key, value); }, Promise.resolve({})); return Promise.all(