mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
AAE-21946 Support JSON paths with non-standard characters for data ta… (#9571)
* AAE-21946 Support JSON paths with non-standard characters for data table widget * add unit test * move path parse to extern helper class
This commit is contained in:
@@ -73,6 +73,11 @@ describe('DataTableWidgetComponent', () => {
|
||||
|
||||
const getPreview = () => fixture.nativeElement.querySelector('[data-automation-id="adf-data-table-widget-preview"]');
|
||||
|
||||
const assertDataRows = (expectedData: WidgetDataTableAdapter) => {
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ProcessServiceCloudTestingModule]
|
||||
@@ -123,9 +128,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockAmericaCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize data source based on field value', () => {
|
||||
@@ -134,9 +137,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockAmericaCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize default json response data source based on field value if path is NOT provided', () => {
|
||||
@@ -145,9 +146,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize default json response data source based on variable if path is NOT provided', () => {
|
||||
@@ -155,9 +154,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize json response data source based on field value if path is provided', () => {
|
||||
@@ -166,9 +163,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize json response data source based on variable if path is provided', () => {
|
||||
@@ -181,9 +176,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize data source based on form variable', () => {
|
||||
@@ -191,9 +184,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should properly initialize data source based on process variable', () => {
|
||||
@@ -201,9 +192,7 @@ describe('DataTableWidgetComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const expectedData = new WidgetDataTableAdapter(mockEuropeCountriesData, mockSchemaDefinition);
|
||||
expectedData.getRows().forEach((row) => (row.cssClass = ''));
|
||||
|
||||
expect(widget.dataSource.getRows()).toEqual(expectedData.getRows());
|
||||
assertDataRows(expectedData);
|
||||
});
|
||||
|
||||
it('should NOT display error if form is in preview state', () => {
|
||||
|
@@ -18,29 +18,17 @@
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
WidgetComponent,
|
||||
FormService,
|
||||
DataTableModule,
|
||||
LogService,
|
||||
FormBaseModule,
|
||||
DataRow,
|
||||
DataColumn
|
||||
} from '@alfresco/adf-core';
|
||||
import { WidgetComponent, FormService, DataTableModule, LogService, FormBaseModule, DataRow, DataColumn } from '@alfresco/adf-core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormCloudService } from '../../../services/form-cloud.service';
|
||||
import { TaskVariableCloud } from '../../../models/task-variable-cloud.model';
|
||||
import { WidgetDataTableAdapter } from './data-table-adapter.widget';
|
||||
import { DataTablePathParserHelper } from './helpers/data-table-path-parser.helper';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
DataTableModule,
|
||||
FormBaseModule
|
||||
],
|
||||
imports: [CommonModule, TranslateModule, DataTableModule, FormBaseModule],
|
||||
selector: 'data-table',
|
||||
templateUrl: './data-table.widget.html',
|
||||
styleUrls: ['./data-table.widget.scss'],
|
||||
@@ -58,7 +46,6 @@ import { WidgetDataTableAdapter } from './data-table-adapter.widget';
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DataTableWidgetComponent extends WidgetComponent implements OnInit {
|
||||
|
||||
dataSource: WidgetDataTableAdapter;
|
||||
dataTableLoadFailed = false;
|
||||
previewState = false;
|
||||
@@ -67,12 +54,9 @@ export class DataTableWidgetComponent extends WidgetComponent implements OnInit
|
||||
private columnsSchema: DataColumn[];
|
||||
private variableName: string;
|
||||
private defaultResponseProperty = 'data';
|
||||
private pathParserHelper = new DataTablePathParserHelper();
|
||||
|
||||
constructor(
|
||||
public formService: FormService,
|
||||
private formCloudService: FormCloudService,
|
||||
private logService: LogService
|
||||
) {
|
||||
constructor(public formService: FormService, private formCloudService: FormCloudService, private logService: LogService) {
|
||||
super(formService);
|
||||
}
|
||||
|
||||
@@ -109,8 +93,8 @@ export class DataTableWidgetComponent extends WidgetComponent implements OnInit
|
||||
const rowsData = fieldValue || this.getDataFromVariable();
|
||||
|
||||
if (rowsData) {
|
||||
const dataFromPath = this.getOptionsFromPath(rowsData, optionsPath);
|
||||
this.rowsData = dataFromPath?.length ? dataFromPath : rowsData as DataRow[];
|
||||
const dataFromPath = this.pathParserHelper.retrieveDataFromPath(rowsData, optionsPath);
|
||||
this.rowsData = (dataFromPath?.length ? dataFromPath : rowsData) as DataRow[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,25 +108,9 @@ export class DataTableWidgetComponent extends WidgetComponent implements OnInit
|
||||
return processVariableDropdownOptions ?? formVariableDropdownOptions;
|
||||
}
|
||||
|
||||
private getOptionsFromPath(data: any, path: string): DataRow[] {
|
||||
const properties = path.split('.');
|
||||
const currentProperty = properties.shift();
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(data, currentProperty)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nestedData = data[currentProperty];
|
||||
|
||||
if (Array.isArray(nestedData)) {
|
||||
return nestedData;
|
||||
}
|
||||
|
||||
return this.getOptionsFromPath(nestedData, properties.join('.'));
|
||||
}
|
||||
|
||||
private getVariableValueByName(variables: TaskVariableCloud[], variableName: string): any {
|
||||
return variables?.find((variable: TaskVariableCloud) => variable?.name === `variables.${variableName}` || variable?.name === variableName)?.value;
|
||||
return variables?.find((variable: TaskVariableCloud) => variable?.name === `variables.${variableName}` || variable?.name === variableName)
|
||||
?.value;
|
||||
}
|
||||
|
||||
private setPreviewState(): void {
|
||||
|
@@ -0,0 +1,88 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DataTablePathParserHelper } from './data-table-path-parser.helper';
|
||||
import {
|
||||
mockEuropeCountriesData,
|
||||
mockJsonNestedResponseEuropeCountriesDataWithSeparatorInPropertyName,
|
||||
mockJsonNestedResponseEuropeCountriesDataWithMultipleSpecialCharacters,
|
||||
mockJsonNestedResponseEuropeCountriesData
|
||||
} from '../../../../mocks/data-table-widget.mock';
|
||||
|
||||
describe('DataTablePathParserHelper', () => {
|
||||
let helper: DataTablePathParserHelper;
|
||||
|
||||
beforeEach(() => {
|
||||
helper = new DataTablePathParserHelper();
|
||||
});
|
||||
|
||||
it('should return the correct data for path with separator in nested brackets', () => {
|
||||
const data = mockJsonNestedResponseEuropeCountriesDataWithSeparatorInPropertyName;
|
||||
const path = 'response.[my.data].[country[data].country]';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(mockEuropeCountriesData);
|
||||
});
|
||||
|
||||
it('should return the correct data for path with special characters except separator (.) in brackets', () => {
|
||||
const data = mockJsonNestedResponseEuropeCountriesDataWithMultipleSpecialCharacters;
|
||||
const path = 'response.[xyz:abc,xyz-abc,xyz_abc,abc+xyz]';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(mockEuropeCountriesData);
|
||||
});
|
||||
|
||||
it('should return the correct data for path with special characters except separator (.) without brackets', () => {
|
||||
const data = mockJsonNestedResponseEuropeCountriesDataWithMultipleSpecialCharacters;
|
||||
const path = 'response.xyz:abc,xyz-abc,xyz_abc,abc+xyz';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(mockEuropeCountriesData);
|
||||
});
|
||||
|
||||
it('should return the correct data for path without separator in brackets', () => {
|
||||
const data = mockJsonNestedResponseEuropeCountriesData;
|
||||
const path = '[response].[my-data]';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(mockEuropeCountriesData);
|
||||
});
|
||||
|
||||
it('should return an empty array if the path does not exist in the data', () => {
|
||||
const data = {};
|
||||
const path = 'nonexistent.path';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return the correct data if the path is nested', () => {
|
||||
const data = { level1: { level2: { level3: { level4: ['parrot', 'fish'] } } } };
|
||||
const path = 'level1.level2.level3.level4';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(['parrot', 'fish']);
|
||||
});
|
||||
|
||||
it('should return the correct data if the path is NOT nested', () => {
|
||||
const data = { pets: ['cat', 'dog'] };
|
||||
const path = 'pets';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(['cat', 'dog']);
|
||||
});
|
||||
|
||||
it('should return the correct data if the path is NOT nested with separator (.) in property name', () => {
|
||||
const data = { 'my.pets': ['cat', 'dog'] };
|
||||
const path = '[my.pets]';
|
||||
const result = helper.retrieveDataFromPath(data, path);
|
||||
expect(result).toEqual(['cat', 'dog']);
|
||||
});
|
||||
});
|
@@ -0,0 +1,50 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class DataTablePathParserHelper {
|
||||
private readonly splitPathRegEx = /\.(?![^[]*\])/g;
|
||||
private readonly removeSquareBracketsRegEx = /^\[(.*)\]$/;
|
||||
|
||||
retrieveDataFromPath(data: any, path: string): any[] {
|
||||
const properties = this.splitPathIntoProperties(path);
|
||||
const currentProperty = this.removeSquareBracketsFromProperty(properties.shift());
|
||||
|
||||
if (!this.isPropertyExistsInData(data, currentProperty)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nestedData = data[currentProperty];
|
||||
|
||||
if (Array.isArray(nestedData)) {
|
||||
return nestedData;
|
||||
}
|
||||
|
||||
return this.retrieveDataFromPath(nestedData, properties.join('.'));
|
||||
}
|
||||
|
||||
private splitPathIntoProperties(path: string): string[] {
|
||||
return path.split(this.splitPathRegEx);
|
||||
}
|
||||
|
||||
private removeSquareBracketsFromProperty(property: string): string {
|
||||
return property.replace(this.removeSquareBracketsRegEx, '$1');
|
||||
}
|
||||
|
||||
private isPropertyExistsInData(data: any, property: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(data, property);
|
||||
}
|
||||
}
|
@@ -85,6 +85,28 @@ export const mockJsonNestedResponseEuropeCountriesData = {
|
||||
}
|
||||
};
|
||||
|
||||
export const mockJsonNestedResponseEuropeCountriesDataWithSeparatorInPropertyName = {
|
||||
response: {
|
||||
empty: [],
|
||||
'my.data': {
|
||||
'country[data].country': mockEuropeCountriesData
|
||||
},
|
||||
data: [
|
||||
{
|
||||
id: 'HR',
|
||||
name: 'Croatia'
|
||||
}
|
||||
],
|
||||
'no-array': {}
|
||||
}
|
||||
};
|
||||
|
||||
export const mockJsonNestedResponseEuropeCountriesDataWithMultipleSpecialCharacters = {
|
||||
response: {
|
||||
'xyz:abc,xyz-abc,xyz_abc,abc+xyz': mockEuropeCountriesData
|
||||
}
|
||||
};
|
||||
|
||||
export const mockAmericaCountriesData = [
|
||||
{
|
||||
id: 'CA',
|
||||
|
Reference in New Issue
Block a user