mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
AAE-22345 Support access to array elements by index (#9663)
* AAE-22345 Update date table parser * AAE-22345 Update data table adapter * AAE-22345 Update * AAE-22345 Align with remarks * AAE-22345 Small update * AAE-22345 Update * AAE-22345 Unit tests fix
This commit is contained in:
committed by
GitHub
parent
cc7550a88c
commit
2d7aa0a61e
@@ -151,6 +151,21 @@ describe('WidgetDataTableAdapter', () => {
|
||||
type: 'json',
|
||||
key: 'person.phoneNumbers',
|
||||
title: 'Phone numbers'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'person.phoneNumbers[0].phoneNumber',
|
||||
title: 'Phone Home'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'person.phoneNumbers[1].phoneNumber',
|
||||
title: 'Phone Work'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'person.cars[0].previousOwners[0].name',
|
||||
title: 'Last Car Owner'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -165,7 +180,10 @@ describe('WidgetDataTableAdapter', () => {
|
||||
'person.phoneNumbers': [
|
||||
{ type: 'home', phoneNumber: '123-456-7890' },
|
||||
{ type: 'work', phoneNumber: '098-765-4321' }
|
||||
]
|
||||
],
|
||||
'person.phoneNumbers[0].phoneNumber': '123-456-7890',
|
||||
'person.phoneNumbers[1].phoneNumber': '098-765-4321',
|
||||
'person.cars[0].previousOwners[0].name': 'Jane Smith'
|
||||
});
|
||||
const expectedSecondRow = new ObjectDataRow({
|
||||
'person.personData.[address.[data]test].city': 'Westlake',
|
||||
@@ -174,20 +192,26 @@ describe('WidgetDataTableAdapter', () => {
|
||||
'person.phoneNumbers': [
|
||||
{ type: 'home', phoneNumber: '123-456-7891' },
|
||||
{ type: 'work', phoneNumber: '321-654-1987' }
|
||||
]
|
||||
],
|
||||
'person.phoneNumbers[0].phoneNumber': '123-456-7891',
|
||||
'person.phoneNumbers[1].phoneNumber': '321-654-1987',
|
||||
'person.cars[0].previousOwners[0].name': 'Bob Johnson'
|
||||
});
|
||||
const expectedColumns = [
|
||||
new ObjectDataColumn({ key: 'person.name', type: 'text', title: 'Name' }),
|
||||
new ObjectDataColumn({ key: 'person.personData.[address.[data]test].city', type: 'text', title: 'City' }),
|
||||
new ObjectDataColumn({ key: 'person.personData.[address.[data]test].street', type: 'text', title: 'Street' }),
|
||||
new ObjectDataColumn({ key: 'person.phoneNumbers', type: 'json', title: 'Phone numbers' })
|
||||
new ObjectDataColumn({ key: 'person.phoneNumbers', type: 'json', title: 'Phone numbers' }),
|
||||
new ObjectDataColumn({ key: 'person.phoneNumbers[0].phoneNumber', type: 'text', title: 'Phone Home' }),
|
||||
new ObjectDataColumn({ key: 'person.phoneNumbers[1].phoneNumber', type: 'text', title: 'Phone Work' }),
|
||||
new ObjectDataColumn({ key: 'person.cars[0].previousOwners[0].name', type: 'text', title: 'Last Car Owner' })
|
||||
];
|
||||
|
||||
expect(rows.length).toBe(2);
|
||||
expect(rows[0]).toEqual(expectedFirstRow);
|
||||
expect(rows[1]).toEqual(expectedSecondRow);
|
||||
|
||||
expect(columns.length).toBe(4);
|
||||
expect(columns.length).toBe(7);
|
||||
expect(columns).toEqual(expectedColumns);
|
||||
});
|
||||
});
|
||||
|
@@ -73,7 +73,18 @@ export class WidgetDataTableAdapter implements DataTableAdapter {
|
||||
}
|
||||
|
||||
private extractPropertyValue(properties: string[], item: any): string {
|
||||
return properties.reduce((acc, property) => (acc ? acc[this.helper.removeSquareBracketsFromProperty(property)] : undefined), item);
|
||||
return properties.reduce((acc, property) => {
|
||||
if (!acc) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const propertyIndexReferences = this.helper.getIndexReferencesFromProperty(property);
|
||||
const isPropertyWithSingleIndexReference = propertyIndexReferences.length === 1;
|
||||
|
||||
const purePropertyName = this.helper.extractPurePropertyName(property);
|
||||
|
||||
return isPropertyWithSingleIndexReference ? acc[purePropertyName]?.[propertyIndexReferences[0]] : acc[purePropertyName];
|
||||
}, item);
|
||||
}
|
||||
|
||||
getColumns(): Array<DataColumn> {
|
||||
|
@@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
import { DataTablePathParserHelper } from './data-table-path-parser.helper';
|
||||
import { mockResponseResultData, mockResultData } from '../mocks/data-table-path-parser.helper.mock';
|
||||
import { mockResponseResultData, mockResponseResultDataWithNestedArray, mockResultData } from '../mocks/data-table-path-parser.helper.mock';
|
||||
|
||||
interface DataTablePathParserTestCase {
|
||||
description: string;
|
||||
path: string;
|
||||
path?: string;
|
||||
data?: any;
|
||||
propertyName?: string;
|
||||
expected?: unknown[];
|
||||
@@ -47,6 +47,11 @@ describe('DataTablePathParserHelper', () => {
|
||||
path: undefined,
|
||||
expected: []
|
||||
},
|
||||
{
|
||||
description: 'empty string',
|
||||
path: '',
|
||||
expected: []
|
||||
},
|
||||
{
|
||||
description: 'nested',
|
||||
data: { level1: { level2: { level3: { level4: ['parrot', 'fish'] } } } },
|
||||
@@ -98,6 +103,16 @@ describe('DataTablePathParserHelper', () => {
|
||||
propertyName: 'file.file[data]file[data]',
|
||||
path: 'response.[file.file[data]file[data]]'
|
||||
},
|
||||
{
|
||||
description: 'with missing closing bracket in outermost square brackets',
|
||||
propertyName: 'file.file[data',
|
||||
path: 'response.[file.file[data]'
|
||||
},
|
||||
{
|
||||
description: 'with missing openning bracket in outermost square brackets',
|
||||
propertyName: 'file.filedata]',
|
||||
path: 'response.[file.filedata]]'
|
||||
},
|
||||
{
|
||||
description: 'with special characters except separator (.) in brackets',
|
||||
propertyName: 'xyz:abc,xyz-abc,xyz_abc,abc+xyz',
|
||||
@@ -112,6 +127,26 @@ describe('DataTablePathParserHelper', () => {
|
||||
description: 'without separator in brackets',
|
||||
propertyName: 'my-data',
|
||||
path: '[response].[my-data]'
|
||||
},
|
||||
{
|
||||
description: 'with property followed by single index reference',
|
||||
propertyName: 'users',
|
||||
path: 'response.users[0].data',
|
||||
data: mockResponseResultDataWithNestedArray('users')
|
||||
},
|
||||
{
|
||||
description: 'with property followed by multiple index references',
|
||||
propertyName: 'users:Array',
|
||||
path: 'response.[users:Array][0][1][12].data',
|
||||
data: mockResponseResultDataWithNestedArray('users:Array'),
|
||||
expected: []
|
||||
},
|
||||
{
|
||||
description: 'when path does NOT point to array',
|
||||
propertyName: 'users',
|
||||
path: 'response.users[0]',
|
||||
data: mockResponseResultDataWithNestedArray('users'),
|
||||
expected: []
|
||||
}
|
||||
];
|
||||
|
||||
@@ -124,4 +159,71 @@ describe('DataTablePathParserHelper', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should split path to properties', () => {
|
||||
const testCases: { path: string; expected: string[] }[] = [
|
||||
{ path: 'response.0', expected: ['response', '0'] },
|
||||
{ path: 'response', expected: ['response'] },
|
||||
{ path: 'response.person.file', expected: ['response', 'person', 'file'] },
|
||||
{ path: 'response.persons[0]', expected: ['response', 'persons[0]'] },
|
||||
{ path: 'response.[persons:Array][0]', expected: ['response', '[persons:Array][0]'] },
|
||||
{ path: 'response.persons[0][1]', expected: ['response', 'persons[0][1]'] },
|
||||
{ path: 'response.persons[0].file.data[4]', expected: ['response', 'persons[0]', 'file', 'data[4]'] },
|
||||
{ path: '', expected: [] },
|
||||
{ path: null, expected: [] },
|
||||
{ path: undefined, expected: [] }
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const result = helper.splitPathIntoProperties(testCase.path);
|
||||
expect(result).toEqual(testCase.expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should extract pure property name', () => {
|
||||
const testCases: { property: string; expected: string }[] = [
|
||||
{ property: '[persons]', expected: 'persons' },
|
||||
{ property: '[persons:data]', expected: 'persons:data' },
|
||||
{ property: '[persons.data]', expected: 'persons.data' },
|
||||
{ property: '[persons.data[1]]', expected: 'persons.data[1]' },
|
||||
{ property: '[persons.data1]]', expected: 'persons.data1]' },
|
||||
{ property: 'persons.data1]', expected: 'persons.data1]' },
|
||||
{ property: 'persons.[data1]', expected: 'persons.[data1]' },
|
||||
{ property: 'persons', expected: 'persons' },
|
||||
{ property: 'persons[0]', expected: 'persons' },
|
||||
{ property: '[persons:Array][0]', expected: 'persons:Array' },
|
||||
{ property: 'persons[0][1]', expected: 'persons' },
|
||||
{ property: '[persons[0].file.data][4]', expected: 'persons[0].file.data' },
|
||||
{ property: '[persons[0].file.data][1][4]', expected: 'persons[0].file.data' },
|
||||
{ property: '[persons.data1]][2][4][23]', expected: 'persons.data1]' },
|
||||
{ property: '', expected: '' },
|
||||
{ property: undefined, expected: '' },
|
||||
{ property: null, expected: '' }
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const result = helper.extractPurePropertyName(testCase.property);
|
||||
expect(result).toEqual(testCase.expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return index references from property', () => {
|
||||
const testCases: { property: string; expected: number[] }[] = [
|
||||
{ property: 'persons[0]', expected: [0] },
|
||||
{ property: '[persons:Array][0]', expected: [0] },
|
||||
{ property: 'persons[0][1][7]', expected: [0, 1, 7] },
|
||||
{ property: '[persons[0].file.data][4]', expected: [4] },
|
||||
{ property: '[persons[0].file.data][1][4]', expected: [1, 4] },
|
||||
{ property: '[persons[0].file.data]', expected: [] },
|
||||
{ property: 'persons', expected: [] },
|
||||
{ property: undefined, expected: [] },
|
||||
{ property: null, expected: [] },
|
||||
{ property: '', expected: [] }
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const result = helper.getIndexReferencesFromProperty(testCase.property);
|
||||
expect(result).toEqual(testCase.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -17,16 +17,25 @@
|
||||
|
||||
export class DataTablePathParserHelper {
|
||||
private readonly removeSquareBracketsRegEx = /^\[(.*)\]$/;
|
||||
private readonly indexReferencesRegEx = /(\[\d+\])+$/;
|
||||
|
||||
retrieveDataFromPath(data: any, path: string): any[] {
|
||||
const properties = this.splitPathIntoProperties(path);
|
||||
const currentProperty = this.removeSquareBracketsFromProperty(properties.shift());
|
||||
|
||||
if (!this.isPropertyExistsInData(data, currentProperty)) {
|
||||
if (!path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nestedData = data[currentProperty];
|
||||
const properties = this.splitPathIntoProperties(path);
|
||||
const currentProperty = properties.shift();
|
||||
const propertyIndexReferences = this.getIndexReferencesFromProperty(currentProperty);
|
||||
const purePropertyName = this.extractPurePropertyName(currentProperty);
|
||||
const isPropertyWithMultipleIndexReferences = propertyIndexReferences.length > 1;
|
||||
|
||||
if (isPropertyWithMultipleIndexReferences || !this.isPropertyExistsInData(data, purePropertyName)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isPropertyWithSingleIndexReference = propertyIndexReferences.length === 1;
|
||||
const nestedData = isPropertyWithSingleIndexReference ? data[purePropertyName]?.[propertyIndexReferences[0]] : data[purePropertyName];
|
||||
|
||||
if (Array.isArray(nestedData)) {
|
||||
return nestedData;
|
||||
@@ -80,7 +89,38 @@ export class DataTablePathParserHelper {
|
||||
return properties;
|
||||
}
|
||||
|
||||
removeSquareBracketsFromProperty(property: string): string {
|
||||
getIndexReferencesFromProperty(property: string): number[] {
|
||||
const match = this.indexReferencesRegEx.exec(property);
|
||||
if (!match) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const indexReferencesString = match[0];
|
||||
const numbersFromBrackets = indexReferencesString.slice(1, -1).split('][').map(Number);
|
||||
|
||||
return numbersFromBrackets;
|
||||
}
|
||||
|
||||
extractPurePropertyName(property: string): string {
|
||||
const propertyIndexReferences = this.getIndexReferencesFromProperty(property);
|
||||
const numberOfIndexReferences = propertyIndexReferences.length;
|
||||
|
||||
if (property == null) {
|
||||
return '';
|
||||
} else if (numberOfIndexReferences !== 0) {
|
||||
return this.removeSquareBracketsAndIndexReferencesFromProperty(property);
|
||||
} else {
|
||||
return this.removeSquareBracketsFromProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
private removeSquareBracketsAndIndexReferencesFromProperty(property: string): string {
|
||||
const propertyWithoutIndexReferences = property?.replace(this.indexReferencesRegEx, '');
|
||||
|
||||
return this.removeSquareBracketsFromProperty(propertyWithoutIndexReferences);
|
||||
}
|
||||
|
||||
private removeSquareBracketsFromProperty(property: string): string {
|
||||
return property?.replace(this.removeSquareBracketsRegEx, '$1');
|
||||
}
|
||||
|
||||
|
@@ -34,6 +34,21 @@ export const mockPersonsData = [
|
||||
type: 'work',
|
||||
phoneNumber: '098-765-4321'
|
||||
}
|
||||
],
|
||||
cars: [
|
||||
{
|
||||
make: 'Toyota',
|
||||
model: 'Corolla',
|
||||
year: 2019,
|
||||
previousOwners: [
|
||||
{
|
||||
name: 'Jane Smith'
|
||||
},
|
||||
{
|
||||
name: 'Jim Down'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -55,6 +70,21 @@ export const mockPersonsData = [
|
||||
type: 'work',
|
||||
phoneNumber: '321-654-1987'
|
||||
}
|
||||
],
|
||||
cars: [
|
||||
{
|
||||
make: 'Honda',
|
||||
model: 'Civic',
|
||||
year: 2018,
|
||||
previousOwners: [
|
||||
{
|
||||
name: 'Bob Johnson'
|
||||
},
|
||||
{
|
||||
name: 'Tom Brown'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -43,3 +43,13 @@ export const mockResponseResultData = (propertyName?: string) => ({
|
||||
[propertyName]: mockResultData
|
||||
}
|
||||
});
|
||||
|
||||
export const mockResponseResultDataWithNestedArray = (propertyName?: string) => ({
|
||||
response: {
|
||||
[propertyName]: [
|
||||
{
|
||||
data: mockResultData
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user