Skip to content

Commit

Permalink
AAE-22345 Support access to array elements by index (#9663)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
wiktord2000 committed May 13, 2024
1 parent cc7550a commit 2d7aa0a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
];

Expand All @@ -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',
Expand All @@ -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);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -47,6 +47,11 @@ describe('DataTablePathParserHelper', () => {
path: undefined,
expected: []
},
{
description: 'empty string',
path: '',
expected: []
},
{
description: 'nested',
data: { level1: { level2: { level3: { level4: ['parrot', 'fish'] } } } },
Expand Down Expand Up @@ -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',
Expand All @@ -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: []
}
];

Expand All @@ -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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,25 @@

export class DataTablePathParserHelper {
private readonly removeSquareBracketsRegEx = /^\[(.*)\]$/;
private readonly indexReferencesRegEx = /(\[\d+\])+$/;

retrieveDataFromPath(data: any, path: string): any[] {
if (!path) {
return [];
}

const properties = this.splitPathIntoProperties(path);
const currentProperty = this.removeSquareBracketsFromProperty(properties.shift());
const currentProperty = properties.shift();
const propertyIndexReferences = this.getIndexReferencesFromProperty(currentProperty);
const purePropertyName = this.extractPurePropertyName(currentProperty);
const isPropertyWithMultipleIndexReferences = propertyIndexReferences.length > 1;

if (!this.isPropertyExistsInData(data, currentProperty)) {
if (isPropertyWithMultipleIndexReferences || !this.isPropertyExistsInData(data, purePropertyName)) {
return [];
}

const nestedData = data[currentProperty];
const isPropertyWithSingleIndexReference = propertyIndexReferences.length === 1;
const nestedData = isPropertyWithSingleIndexReference ? data[purePropertyName]?.[propertyIndexReferences[0]] : data[purePropertyName];

if (Array.isArray(nestedData)) {
return nestedData;
Expand Down Expand Up @@ -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');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
]
}
]
}
},
Expand All @@ -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'
}
]
}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,13 @@ export const mockResponseResultData = (propertyName?: string) => ({
[propertyName]: mockResultData
}
});

export const mockResponseResultDataWithNestedArray = (propertyName?: string) => ({
response: {
[propertyName]: [
{
data: mockResultData
}
]
}
});

0 comments on commit 2d7aa0a

Please sign in to comment.