Skip to content

Commit

Permalink
feat(core): export data based on dot notation structure definition (#686
Browse files Browse the repository at this point in the history
)

* export data based on dot notation structure

* fix type

* fix type

* fix lint

* use set instead of reduceRight+merge on getMappingRecordValues (exportData)

* delete coms
  • Loading branch information
jrmyb authored Jan 6, 2025
1 parent 5a7fd10 commit 177f197
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 5 deletions.
2 changes: 1 addition & 1 deletion apps/core/src/domain/attribute/attributeDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
},
Expand Down
112 changes: 112 additions & 0 deletions apps/core/src/domain/export/exportDomain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IAttributeDomain> = {
getAttributeProperties: jest.fn()
};

const mockUtils: Mockify<IUtils> = {
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<IRecordDomain> = {
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'
}
]);
});
});
});
8 changes: 4 additions & 4 deletions apps/core/src/domain/export/exportDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -38,7 +38,7 @@ export interface IExportDomain {
jsonMapping: string,
elements: Array<{[libraryId: string]: string}>,
ctx: IQueryInfos
): Promise<Array<{[mappingKey: string]: string}>>;
): Promise<Array<{[key: string]: any}>>;
}

export interface IExportDomainDeps {
Expand Down Expand Up @@ -205,15 +205,15 @@ export default function ({
jsonMapping: string,
recordsToExport: Array<{[libraryId: string]: string}>,
ctx: IQueryInfos
): Promise<Array<{[mappingKey: string]: string}>> {
): Promise<Array<{[key: string]: any}>> {
const mapping = JSON.parse(jsonMapping) as Record<string, string>;
const mappingKeysByLibrary = _getMappingKeysByLibrary(mapping);

const getMappingRecordValues = async (keys: string[], libraryId: string, recordId: string) =>
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(
Expand Down

0 comments on commit 177f197

Please sign in to comment.