From cf0899df57faabee53585a482955ccd6fc4caf9c Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Fri, 26 Jan 2024 08:08:26 +0100 Subject: [PATCH 01/10] feat: service to export data --- libs/portal-integration-angular/src/index.ts | 2 +- .../interactive-data-view.component.ts | 6 +- .../src/lib/services/export-data.service.ts | 95 +++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 libs/portal-integration-angular/src/lib/services/export-data.service.ts diff --git a/libs/portal-integration-angular/src/index.ts b/libs/portal-integration-angular/src/index.ts index 339d1a37..8d3b673f 100644 --- a/libs/portal-integration-angular/src/index.ts +++ b/libs/portal-integration-angular/src/index.ts @@ -68,6 +68,7 @@ export * from './lib/services/initialize-module-guard.service' export * from './lib/services/userprofile-api.service' export * from './lib/services/portal-dialog.service' export * from './lib/services/user.service' +export * from './lib/services/export-data.service' // pipes export * from './lib/core/pipes/dynamic.pipe' @@ -89,7 +90,6 @@ export * from './lib/model/person.model' export * from './lib/model/page-info.model' export * from './lib/model/portal' export * from './lib/model/user-profile.model' -export * from './lib/model/user-profile.model' export * from './lib/model/data-table-column.model' export * from './lib/model/column-type.model' export * from './lib/model/data-sort-direction' diff --git a/libs/portal-integration-angular/src/lib/core/components/interactive-data-view/interactive-data-view.component.ts b/libs/portal-integration-angular/src/lib/core/components/interactive-data-view/interactive-data-view.component.ts index 26c7c449..0190851f 100644 --- a/libs/portal-integration-angular/src/lib/core/components/interactive-data-view/interactive-data-view.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/interactive-data-view/interactive-data-view.component.ts @@ -70,7 +70,8 @@ export class InteractiveDataViewComponent implements OnInit { @Output() deleteItem = new EventEmitter() @Output() viewItem = new EventEmitter() @Output() editItem = new EventEmitter() - @Output() dataViewLayoutChange: EventEmitter = new EventEmitter() + @Output() dataViewLayoutChange = new EventEmitter<'grid' | 'list' | 'table'>() + @Output() displayedColumnsChange = new EventEmitter() displayedColumns: DataTableColumn[] = [] selectedGroupKey = '' isDeleteItemObserved: boolean | undefined @@ -135,6 +136,7 @@ export class InteractiveDataViewComponent implements OnInit { column.predefinedGroupKeys?.includes(this.defaultGroupKey) ) } + this.displayedColumnsChange.emit(this.displayedColumns) if (!this.groupSelectionNoGroupSelectedKey) { this.groupSelectionNoGroupSelectedKey = 'OCX_INTERACTIVE_DATA_VIEW.NO_GROUP_SELECTED' } @@ -188,6 +190,7 @@ export class InteractiveDataViewComponent implements OnInit { onColumnGroupSelectionChange(event: GroupSelectionChangedEvent) { this.displayedColumns = event.activeColumns this.selectedGroupKey = event.groupKey + this.displayedColumnsChange.emit(this.displayedColumns) } registerEventListenerForDataView() { @@ -220,5 +223,6 @@ export class InteractiveDataViewComponent implements OnInit { onColumnSelectionChange(event: ColumnSelectionChangedEvent) { this.displayedColumns = event.activeColumns this.selectedGroupKey = this.customGroupKey + this.displayedColumnsChange.emit(this.displayedColumns) } } diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts new file mode 100644 index 00000000..1a6deb3b --- /dev/null +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -0,0 +1,95 @@ +import { Inject, Injectable, LOCALE_ID } from '@angular/core' +import { TranslateService } from '@ngx-translate/core' +import { firstValueFrom, map, Observable, of } from 'rxjs' +import { DateUtils } from '../core/utils/dateutils' +import { ObjectUtils } from '../core/utils/objectutils' +import { ColumnType } from '../model/column-type.model' + +@Injectable() +export class ExportDataService { + constructor( + private dateUtils: DateUtils, + private translateService: TranslateService, + @Inject(LOCALE_ID) private locale: string + ) {} + + async export( + columns: { id: string; nameKey: string; columnType: ColumnType }[], + data: Partial>[], + fileName: string + ): Promise { + const flattenedData = data.map((d) => + columns.reduce((obj, c) => ({ ...obj, [c.id]: ObjectUtils.resolveFieldData(d, c.id) }), {}) + ) + const translatedData = await firstValueFrom(this.translateData(columns, flattenedData)) + const dataToExport = this.formatData(columns, translatedData) + const delimiter = this.locale.startsWith('de') ? ';' : ',' + const dataString = dataToExport + .map((d) => columns.reduce((arr: unknown[], c) => [...arr, d[c.id]], []).join(delimiter)) + .join('\r\n') + const headerString = (await firstValueFrom(this.translateColumnNames(columns))).map((c) => c.name).join(delimiter) + const csvString = headerString + '\r\n' + dataString + + let blob = new Blob(['\ufeff' + csvString], { + type: 'text/csv;charset=utf-8;', + }) + let dwldLink = document.createElement('a') + let url = URL.createObjectURL(blob) + dwldLink.setAttribute('href', url) + + dwldLink.setAttribute('download', fileName) + dwldLink.click() + } + + private translateColumnNames( + columns: { id: string; nameKey: string; columnType: ColumnType }[] + ): Observable<{ id: string; name: string; columnType: ColumnType }[]> { + return this.translateService + .get(columns.map((c) => c.nameKey)) + .pipe(map((translations) => columns.map((c) => ({ ...c, name: translations[c.nameKey] })))) + } + + private formatData( + columns: { id: string; nameKey: string; columnType: ColumnType }[], + data: Record[] + ): { [columnId: string]: unknown }[] { + return data.map((d) => + columns.reduce((obj, c) => { + if (c.columnType === ColumnType.DATE || c.columnType === ColumnType.RELATIVE_DATE) { + return { + ...obj, + [c.id]: this.dateUtils.localizedDate(String(d[c.id])), + } + } + return { ...obj, [c.id]: d[c.id] } + }, {}) + ) + } + + private translateData( + columns: { id: string; nameKey: string; columnType: ColumnType }[], + data: Record[] + ): Observable<{ [columnId: string]: unknown }[]> { + let translationKeys: string[] = [] + const translatedColumns = columns.filter((c) => c.columnType === ColumnType.TRANSLATION_KEY) + translatedColumns.forEach((c) => { + translationKeys = [...translationKeys, ...data.map((i) => i[c.id]?.toString() ?? '')] + }) + if (translationKeys.length) { + return this.translateService.get(translationKeys).pipe( + map((translatedValues: Record) => { + return data.map((d) => + columns.reduce( + (obj, c) => ({ + ...obj, + [c.id]: c.columnType === ColumnType.TRANSLATION_KEY ? translatedValues[String(d[c.id])] : d[c.id], + }), + {} + ) + ) + }) + ) + } + return of(data) + } +} From 4ef90ae6cc407cd05ffa262f08870ea931ce748d Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Fri, 26 Jan 2024 08:14:12 +0100 Subject: [PATCH 02/10] fix: lint error fix --- .../src/lib/services/export-data.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts index 1a6deb3b..230a7b54 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data.service.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -30,11 +30,11 @@ export class ExportDataService { const headerString = (await firstValueFrom(this.translateColumnNames(columns))).map((c) => c.name).join(delimiter) const csvString = headerString + '\r\n' + dataString - let blob = new Blob(['\ufeff' + csvString], { + const blob = new Blob(['\ufeff' + csvString], { type: 'text/csv;charset=utf-8;', }) - let dwldLink = document.createElement('a') - let url = URL.createObjectURL(blob) + const dwldLink = document.createElement('a') + const url = URL.createObjectURL(blob) dwldLink.setAttribute('href', url) dwldLink.setAttribute('download', fileName) From fcb05c36454262fc8ddcd809f614f5f20e398000 Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Fri, 26 Jan 2024 08:42:43 +0100 Subject: [PATCH 03/10] feat: add encodeParam, edit createTranslateLoader --- .../src/lib/core/portal-core.module.ts | 6 ++++-- .../lib/core/utils/create-translate-loader.utils.ts | 12 ++++++++++-- .../lib/core/utils/portal-api-configuration.utils.ts | 8 ++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts index 94bfd1e4..10237406 100644 --- a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts +++ b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts @@ -56,7 +56,6 @@ import { HelpItemEditorComponent } from './components/help-item-editor/help-item import { NoHelpItemComponent } from './components/no-help-item/no-help-item.component' import { DataListGridComponent } from './components/data-list-grid/data-list-grid.component' import { PrimeNgModule } from './primeng.module' -import { MockAuthService } from '../mock-auth/mock-auth.service' import { ConfirmDialogModule } from 'primeng/confirmdialog' import { DataTableComponent } from './components/data-table/data-table.component' import de from '@angular/common/locales/de' @@ -85,6 +84,8 @@ import { UserService } from '../services/user.service' import { UserProfileAPIService } from '../services/userprofile-api.service' import { createTranslateLoader } from './utils/create-translate-loader.utils' import { MessageService } from 'primeng/api' +import { ExportDataService } from '../services/export-data.service' +import { InitializeModuleGuard } from '../services/initialize-module-guard.service' export class MyMissingTranslationHandler implements MissingTranslationHandler { handle(params: MissingTranslationHandlerParams) { @@ -167,7 +168,6 @@ export class MyMissingTranslationHandler implements MissingTranslationHandler { ], providers: [ ConfigurationService, - MockAuthService, { provide: LOCALE_ID, useFactory: (translate: TranslateService) => { @@ -176,6 +176,8 @@ export class MyMissingTranslationHandler implements MissingTranslationHandler { }, deps: [TranslateService], }, + ExportDataService, + InitializeModuleGuard, ], exports: [ AnnouncementBannerComponent, diff --git a/libs/portal-integration-angular/src/lib/core/utils/create-translate-loader.utils.ts b/libs/portal-integration-angular/src/lib/core/utils/create-translate-loader.utils.ts index 7a09e908..a33296b8 100644 --- a/libs/portal-integration-angular/src/lib/core/utils/create-translate-loader.utils.ts +++ b/libs/portal-integration-angular/src/lib/core/utils/create-translate-loader.utils.ts @@ -1,3 +1,4 @@ +import { Location } from '@angular/common' import { HttpClient } from '@angular/common/http' import { TranslateLoader } from '@ngx-translate/core' import { TranslateHttpLoader } from '@ngx-translate/http-loader' @@ -12,9 +13,16 @@ export function createTranslateLoader(http: HttpClient, appStateService: AppStat filter(([, isLoading]) => !isLoading), map(([currentMfe]) => { return new TranslateCombinedLoader( - new TranslateHttpLoader(http, `${currentMfe.remoteBaseUrl}/assets/i18n/`, '.json'), + // translations of shell or of app in standalone mode new TranslateHttpLoader(http, `./assets/i18n/`, '.json'), - new TranslateHttpLoader(http, `./onecx-portal-lib/assets/i18n/`, '.json') + // translations of portal-integration-angular of app + new TranslateHttpLoader( + http, + Location.joinWithSlash(currentMfe.remoteBaseUrl, `onecx-portal-lib/assets/i18n/`), + '.json' + ), + // translations of the app + new TranslateHttpLoader(http, Location.joinWithSlash(currentMfe.remoteBaseUrl, `assets/i18n/`), '.json') ) }) ) diff --git a/libs/portal-integration-angular/src/lib/core/utils/portal-api-configuration.utils.ts b/libs/portal-integration-angular/src/lib/core/utils/portal-api-configuration.utils.ts index 463bf790..fbfe0fc3 100644 --- a/libs/portal-integration-angular/src/lib/core/utils/portal-api-configuration.utils.ts +++ b/libs/portal-integration-angular/src/lib/core/utils/portal-api-configuration.utils.ts @@ -5,6 +5,7 @@ import { ConfigurationService } from '../../services/configuration.service' type Config = { credentials: { [key: string]: string | (() => string | undefined) } + encodeParam: (param: unknown) => string selectHeaderContentType(contentTypes: string[]): string | undefined selectHeaderAccept(accepts: string[]): string | undefined isJsonMime(mime: string): boolean @@ -29,6 +30,13 @@ export class PortalApiConfiguration { this.configuration.credentials = value } + get encodeParam(): (param: unknown) => string { + return this.configuration.encodeParam + } + set encocdeParam(value: (param: unknown) => string) { + this.configuration.encodeParam = value + } + constructor( private configurationClassOfGenerator: unknown, private apiPrefix: string, From c269a2bf22bc3bcca9ae78ff32780a93423ed85d Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Fri, 26 Jan 2024 16:30:05 +0100 Subject: [PATCH 04/10] feat: write unit test --- .../lib/services/export-data-service.spec.ts | 282 ++++++++++++++++++ .../src/lib/services/export-data.service.ts | 1 + package-lock.json | 13 + package.json | 3 +- 4 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts diff --git a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts new file mode 100644 index 00000000..e9d02c80 --- /dev/null +++ b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts @@ -0,0 +1,282 @@ +import { TestBed } from '@angular/core/testing' +import { TranslateModule, TranslateService } from '@ngx-translate/core' +import { TranslateTestingModule } from 'ngx-translate-testing' +import { ColumnType } from '../model/column-type.model' +import { ExportDataService } from './export-data.service' + +describe('ExportDataService', () => { + class ElementMock { + attributes: Record = {} + setAttribute(name: string, value: unknown) { + this.attributes[name] = value + } + click() {} + } + + let blobs: Blob[] = [] + + let translateService: TranslateService + let exportDataService: ExportDataService + + const ENGLISH_LANGUAGE = 'en' + const ENGLISH_TRANSLATIONS = { + SOME_STATUS: 'some status', + STATUS_EXAMPLE: 'some status example', + STATUS_NAME_1: 'status name 1', + STATUS_NAME_2: 'status name 2', + STATUS_NAME_3: 'status name 3', + COLUMN_HEADER_NAME: { + NAME: 'Name', + STATUS: 'Status', + DESCRIPTION: 'Description', + RESPONSIBLE: 'Responsible', + START_DATE: 'Start date', + END_DATE: 'End date', + MODIFICATION_DATE: 'Modification date', + CREATION_USER: 'Creation user', + TEST_NUMBER: 'Test number', + }, + } + + const GERMAN_LANGUAGE = 'de' + const GERMAN_TRANSLATIONS = { + SOME_STATUS: 'irgendein Status', + STATUS_EXAMPLE: 'irgendein Beispielstatus', + STATUS_NAME_1: 'Status Name 1', + STATUS_NAME_2: 'Status Name 2', + STATUS_NAME_3: 'Status Name 3', + COLUMN_HEADER_NAME: { + NAME: 'Name', + STATUS: 'Status', + DESCRIPTION: 'Beschreibung', + RESPONSIBLE: 'Verantwortlich', + START_DATE: 'Startdatum', + END_DATE: 'Enddatum', + MODIFICATION_DATE: 'Änderungsdatum', + CREATION_USER: 'Erstellungsbenutzer', + TEST_NUMBER: 'Testnummer', + }, + } + + const TRANSLATIONS = { + [ENGLISH_LANGUAGE]: ENGLISH_TRANSLATIONS, + [GERMAN_LANGUAGE]: GERMAN_TRANSLATIONS, + } + + const mockData = [ + { + version: 0, + creationDate: '2023-09-12T09:34:11.997048Z', + creationUser: 'creation user', + modificationDate: '2023-09-12T09:34:11.997048Z', + modificationUser: '', + id: '195ee34e-41c6-47b7-8fc4-3f245dee7651', + name: 'some name', + description: '', + status: 'SOME_STATUS', + responsible: 'someone responsible', + endDate: '2023-09-14T09:34:09Z', + startDate: '2023-09-13T09:34:05Z', + imagePath: '/path/to/image', + testNumber: '1', + }, + { + version: 0, + creationDate: '2023-09-12T09:33:58.544494Z', + creationUser: '', + modificationDate: '2023-09-12T09:33:58.544494Z', + modificationUser: '', + id: '5f8bb05b-d089-485e-a234-0bb6ff25234e', + name: 'example', + description: 'example description', + status: 'STATUS_EXAMPLE', + responsible: '', + endDate: '2023-09-13T09:33:55Z', + startDate: '2023-09-12T09:33:53Z', + imagePath: '', + testNumber: '3.141', + }, + { + version: 0, + creationDate: '2023-09-12T09:34:27.184086Z', + creationUser: '', + modificationDate: '2023-09-12T09:34:27.184086Z', + modificationUser: '', + id: 'cf9e7d6b-5362-46af-91f8-62f7ef5c6064', + name: 'name 1', + description: '', + status: 'STATUS_NAME_1', + responsible: '', + endDate: '2023-09-15T09:34:24Z', + startDate: '2023-09-14T09:34:22Z', + imagePath: '', + testNumber: '123456789', + }, + { + version: 0, + creationDate: '2023-09-12T09:34:27.184086Z', + creationUser: '', + modificationDate: '2023-09-12T09:34:27.184086Z', + modificationUser: '', + id: 'cf9e7d6b-5362-46af-91f8-62f7ef5c6064', + name: 'name 2', + description: '', + status: 'STATUS_NAME_2', + responsible: '', + endDate: '2023-09-15T09:34:24Z', + startDate: '2023-09-14T09:34:22Z', + imagePath: '', + testNumber: '12345.6789', + }, + { + version: 0, + creationDate: '2023-09-12T09:34:27.184086Z', + creationUser: '', + modificationDate: '2023-09-12T09:34:27.184086Z', + modificationUser: '', + id: 'cf9e7d6b-5362-46af-91f8-62f7ef5c6064', + name: 'name 3', + description: '', + status: 'STATUS_NAME_3', + responsible: '', + endDate: '2023-09-15T09:34:24Z', + startDate: '2023-09-14T09:34:22Z', + imagePath: '', + testNumber: '7.1', + }, + ] + const mockColumns = [ + { + columnType: ColumnType.STRING, + id: 'name', + nameKey: 'COLUMN_HEADER_NAME.NAME', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.DEFAULT', 'PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.STRING, + id: 'description', + nameKey: 'COLUMN_HEADER_NAME.DESCRIPTION', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.DEFAULT', 'PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.DATE, + id: 'startDate', + nameKey: 'COLUMN_HEADER_NAME.START_DATE', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.DATE, + id: 'endDate', + nameKey: 'COLUMN_HEADER_NAME.END_DATE', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.TRANSLATION_KEY, + id: 'status', + nameKey: 'COLUMN_HEADER_NAME.STATUS', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.DEFAULT', 'PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.STRING, + id: 'responsible', + nameKey: 'COLUMN_HEADER_NAME.RESPONSIBLE', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.DEFAULT', 'PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.RELATIVE_DATE, + id: 'modificationDate', + nameKey: 'COLUMN_HEADER_NAME.MODIFICATION_DATE', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.STRING, + id: 'creationUser', + nameKey: 'COLUMN_HEADER_NAME.CREATION_USER', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.FULL'], + }, + { + columnType: ColumnType.NUMBER, + id: 'testNumber', + nameKey: 'COLUMN_HEADER_NAME.TEST_NUMBER', + filterable: true, + sortable: true, + predefinedGroupKeys: ['PREDEFINED_GROUP.EXTENDED', 'PREDEFINED_GROUP.FULL'], + }, + ] + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [], + imports: [TranslateModule.forRoot(), TranslateTestingModule.withTranslations(TRANSLATIONS)], + providers: [ExportDataService], + }).compileComponents() + + translateService = TestBed.inject(TranslateService) + translateService.use('en') + + exportDataService = TestBed.inject(ExportDataService) + + blobs = [] + }) + + it('should export data as csv in en', async () => { + const expectedHref = + 'Name,Description,Start date,End date,Status,Responsible,Modification date,Creation user,Test number' + + '\r\nsome name,,Sep 13, 2023, 11:34:05 AM,Sep 14, 2023, 11:34:09 AM,some status,someone responsible,Sep 12, 2023, 11:34:11 AM,creation user,1' + + '\r\nexample,example description,Sep 12, 2023, 11:33:53 AM,Sep 13, 2023, 11:33:55 AM,some status example,,Sep 12, 2023, 11:33:58 AM,,3.141' + + '\r\nname 1,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 1,,Sep 12, 2023, 11:34:27 AM,,123456789' + + '\r\nname 2,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 2,,Sep 12, 2023, 11:34:27 AM,,12345.6789' + + '\r\nname 3,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 3,,Sep 12, 2023, 11:34:27 AM,,7.1' + const expectedFilename = 'some-test.csv' + const mock = new ElementMock() + + jest.spyOn(document, 'createElement').mockReturnValue(mock) + URL.createObjectURL = jest.fn().mockImplementation((b: Blob) => { + blobs.push(b) + return (blobs.length - 1).toString() + }) + await exportDataService.export(mockColumns, mockData, 'some-test.csv') + + expect(expectedHref).toEqual(await blobs[Number(mock.attributes['href'])].text()) + expect(expectedFilename).toEqual(mock.attributes['download']) + }) + + it('should export data as csv in de', async () => { + translateService.use('de') + const expectedFilename = 'some-test.csv' + const expectedHref = + 'Name,Beschreibung,Startdatum,Enddatum,Status,Verantwortlich,Änderungsdatum,Erstellungsbenutzer,Testnummer' + + '\r\nsome name,,Sep 13, 2023, 11:34:05 AM,Sep 14, 2023, 11:34:09 AM,irgendein Status,someone responsible,Sep 12, 2023, 11:34:11 AM,creation user,1' + + '\r\nexample,example description,Sep 12, 2023, 11:33:53 AM,Sep 13, 2023, 11:33:55 AM,irgendein Beispielstatus,,Sep 12, 2023, 11:33:58 AM,,3.141' + + '\r\nname 1,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 1,,Sep 12, 2023, 11:34:27 AM,,123456789' + + '\r\nname 2,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 2,,Sep 12, 2023, 11:34:27 AM,,12345.6789' + + '\r\nname 3,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 3,,Sep 12, 2023, 11:34:27 AM,,7.1' + const mock = new ElementMock() + + jest.spyOn(document, 'createElement').mockReturnValue(mock) + URL.createObjectURL = jest.fn().mockImplementation((b: Blob) => { + blobs.push(b) + return (blobs.length - 1).toString() + }) + await exportDataService.export(mockColumns, mockData, 'some-test.csv') + + expect(expectedHref).toEqual(await blobs[Number(mock.attributes['href'])].text()) + expect(expectedFilename).toEqual(mock.attributes['download']) + }) +}) diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts index 230a7b54..43d182da 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data.service.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -4,6 +4,7 @@ import { firstValueFrom, map, Observable, of } from 'rxjs' import { DateUtils } from '../core/utils/dateutils' import { ObjectUtils } from '../core/utils/objectutils' import { ColumnType } from '../model/column-type.model' +const Blob = require('blob-polyfill').Blob; @Injectable() export class ExportDataService { diff --git a/package-lock.json b/package-lock.json index d8faa462..c9250b8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,7 @@ "@types/node": "~18.7.1", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", + "blob-polyfill": "^7.0.20220408", "conventional-changelog-conventionalcommits": "^7.0.2", "esbuild": "^0.19.2", "eslint": "~8.46.0", @@ -19747,6 +19748,12 @@ "node": ">= 6" } }, + "node_modules/blob-polyfill": { + "version": "7.0.20220408", + "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-7.0.20220408.tgz", + "integrity": "sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.1", "dev": true, @@ -53417,6 +53424,12 @@ } } }, + "blob-polyfill": { + "version": "7.0.20220408", + "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-7.0.20220408.tgz", + "integrity": "sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ==", + "dev": true + }, "body-parser": { "version": "1.20.1", "dev": true, diff --git a/package.json b/package.json index e076679f..aaac76b0 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "ngx-color": "^8.0.3", "primeflex": "^3.3.0", "primeicons": "^6.0.1", + "primeng": "15.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "primeng": "15.2.1", "rxjs": "7.8.1", "tslib": "^2.5.0", "zod": "^3.22.1", @@ -78,6 +78,7 @@ "@types/node": "~18.7.1", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", + "blob-polyfill": "^7.0.20220408", "conventional-changelog-conventionalcommits": "^7.0.2", "esbuild": "^0.19.2", "eslint": "~8.46.0", From bd10f0a1ee5e2643e191f9bc38c306097c6fa8da Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Tue, 30 Jan 2024 10:43:40 +0100 Subject: [PATCH 05/10] fix: some minor fixes --- .../src/lib/core/portal-core.module.ts | 4 --- .../src/lib/core/utils/dateutils.ts | 9 ++---- .../lib/services/export-data-service.spec.ts | 31 +++++++++++++------ .../src/lib/services/export-data.service.ts | 3 +- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts index 9190fe99..1b7a598d 100644 --- a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts +++ b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts @@ -83,8 +83,6 @@ import { UserService } from '../services/user.service' import { UserProfileAPIService } from '../services/userprofile-api.service' import { createTranslateLoader } from './utils/create-translate-loader.utils' import { MessageService } from 'primeng/api' -import { ExportDataService } from '../services/export-data.service' -import { InitializeModuleGuard } from '../services/initialize-module-guard.service' export class PortalMissingTranslationHandler implements MissingTranslationHandler { handle(params: MissingTranslationHandlerParams) { @@ -174,8 +172,6 @@ export class PortalMissingTranslationHandler implements MissingTranslationHandle }, deps: [UserService], }, - ExportDataService, - InitializeModuleGuard, ], exports: [ AnnouncementBannerComponent, diff --git a/libs/portal-integration-angular/src/lib/core/utils/dateutils.ts b/libs/portal-integration-angular/src/lib/core/utils/dateutils.ts index 6875999c..34a3a796 100644 --- a/libs/portal-integration-angular/src/lib/core/utils/dateutils.ts +++ b/libs/portal-integration-angular/src/lib/core/utils/dateutils.ts @@ -1,11 +1,10 @@ -import { TranslateService } from '@ngx-translate/core' -import { Injectable } from '@angular/core' +import { Inject, Injectable, LOCALE_ID } from '@angular/core' @Injectable({ providedIn: 'root', }) export class DateUtils { - constructor(private translateService: TranslateService) {} + constructor(@Inject(LOCALE_ID) protected locale: string) {} options: Intl.DateTimeFormatOptions = { month: 'short', @@ -18,9 +17,7 @@ export class DateUtils { localizedDate(date: string | number | Date | undefined): string { return date - ? new Intl.DateTimeFormat(this.translateService.getBrowserLang(), this.options).format( - date instanceof Date ? date : new Date(date) - ) + ? new Intl.DateTimeFormat(this.locale, this.options).format(date instanceof Date ? date : new Date(date)) : '' } } diff --git a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts index e9d02c80..4fced405 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts @@ -1,6 +1,8 @@ import { TestBed } from '@angular/core/testing' import { TranslateModule, TranslateService } from '@ngx-translate/core' +import 'blob-polyfill' import { TranslateTestingModule } from 'ngx-translate-testing' +import { DateUtils } from '../core/utils/dateutils' import { ColumnType } from '../model/column-type.model' import { ExportDataService } from './export-data.service' @@ -10,13 +12,16 @@ describe('ExportDataService', () => { setAttribute(name: string, value: unknown) { this.attributes[name] = value } - click() {} + click() { + // do click + } } let blobs: Blob[] = [] let translateService: TranslateService let exportDataService: ExportDataService + let dateUtils: DateUtils const ENGLISH_LANGUAGE = 'en' const ENGLISH_TRANSLATIONS = { @@ -224,18 +229,21 @@ describe('ExportDataService', () => { await TestBed.configureTestingModule({ declarations: [], imports: [TranslateModule.forRoot(), TranslateTestingModule.withTranslations(TRANSLATIONS)], - providers: [ExportDataService], + providers: [ExportDataService, DateUtils], }).compileComponents() translateService = TestBed.inject(TranslateService) - translateService.use('en') - exportDataService = TestBed.inject(ExportDataService) + dateUtils = TestBed.inject(DateUtils) blobs = [] }) it('should export data as csv in en', async () => { + translateService.use('en') + ;(exportDataService).locale = 'en' + ;(dateUtils).locale = 'en' + const expectedHref = 'Name,Description,Start date,End date,Status,Responsible,Modification date,Creation user,Test number' + '\r\nsome name,,Sep 13, 2023, 11:34:05 AM,Sep 14, 2023, 11:34:09 AM,some status,someone responsible,Sep 12, 2023, 11:34:11 AM,creation user,1' + @@ -259,14 +267,17 @@ describe('ExportDataService', () => { it('should export data as csv in de', async () => { translateService.use('de') + ;(exportDataService).locale = 'de' + ;(dateUtils).locale = 'de' + const expectedFilename = 'some-test.csv' const expectedHref = - 'Name,Beschreibung,Startdatum,Enddatum,Status,Verantwortlich,Änderungsdatum,Erstellungsbenutzer,Testnummer' + - '\r\nsome name,,Sep 13, 2023, 11:34:05 AM,Sep 14, 2023, 11:34:09 AM,irgendein Status,someone responsible,Sep 12, 2023, 11:34:11 AM,creation user,1' + - '\r\nexample,example description,Sep 12, 2023, 11:33:53 AM,Sep 13, 2023, 11:33:55 AM,irgendein Beispielstatus,,Sep 12, 2023, 11:33:58 AM,,3.141' + - '\r\nname 1,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 1,,Sep 12, 2023, 11:34:27 AM,,123456789' + - '\r\nname 2,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 2,,Sep 12, 2023, 11:34:27 AM,,12345.6789' + - '\r\nname 3,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,Status Name 3,,Sep 12, 2023, 11:34:27 AM,,7.1' + 'Name;Beschreibung;Startdatum;Enddatum;Status;Verantwortlich;Änderungsdatum;Erstellungsbenutzer;Testnummer' + + '\r\nsome name;;13. Sept. 2023, 11:34:05;14. Sept. 2023, 11:34:09;irgendein Status;someone responsible;12. Sept. 2023, 11:34:11;creation user;1' + + '\r\nexample;example description;12. Sept. 2023, 11:33:53;13. Sept. 2023, 11:33:55;irgendein Beispielstatus;;12. Sept. 2023, 11:33:58;;3.141' + + '\r\nname 1;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 1;;12. Sept. 2023, 11:34:27;;123456789' + + '\r\nname 2;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 2;;12. Sept. 2023, 11:34:27;;12345.6789' + + '\r\nname 3;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 3;;12. Sept. 2023, 11:34:27;;7.1' const mock = new ElementMock() jest.spyOn(document, 'createElement').mockReturnValue(mock) diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts index 43d182da..c720dc33 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data.service.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -4,9 +4,8 @@ import { firstValueFrom, map, Observable, of } from 'rxjs' import { DateUtils } from '../core/utils/dateutils' import { ObjectUtils } from '../core/utils/objectutils' import { ColumnType } from '../model/column-type.model' -const Blob = require('blob-polyfill').Blob; -@Injectable() +@Injectable({ providedIn: 'any' }) export class ExportDataService { constructor( private dateUtils: DateUtils, From b2e6660dfe016c32d26dd0f8d240d682f70b5544 Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Tue, 30 Jan 2024 11:41:56 +0100 Subject: [PATCH 06/10] fix: localized date in expected data --- .../lib/services/export-data-service.spec.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts index 4fced405..73ec95bb 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts @@ -246,11 +246,11 @@ describe('ExportDataService', () => { const expectedHref = 'Name,Description,Start date,End date,Status,Responsible,Modification date,Creation user,Test number' + - '\r\nsome name,,Sep 13, 2023, 11:34:05 AM,Sep 14, 2023, 11:34:09 AM,some status,someone responsible,Sep 12, 2023, 11:34:11 AM,creation user,1' + - '\r\nexample,example description,Sep 12, 2023, 11:33:53 AM,Sep 13, 2023, 11:33:55 AM,some status example,,Sep 12, 2023, 11:33:58 AM,,3.141' + - '\r\nname 1,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 1,,Sep 12, 2023, 11:34:27 AM,,123456789' + - '\r\nname 2,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 2,,Sep 12, 2023, 11:34:27 AM,,12345.6789' + - '\r\nname 3,,Sep 14, 2023, 11:34:22 AM,Sep 15, 2023, 11:34:24 AM,status name 3,,Sep 12, 2023, 11:34:27 AM,,7.1' + '\r\nsome name,,' + dateUtils.localizedDate('2023-09-13T09:34:05Z') + ',' + dateUtils.localizedDate('2023-09-14T09:34:09Z') + ',some status,someone responsible,' + dateUtils.localizedDate('2023-09-12T09:34:11.997048Z') + ',creation user,1' + + '\r\nexample,example description,'+ dateUtils.localizedDate('2023-09-12T09:33:53Z') + ',' + dateUtils.localizedDate('2023-09-13T09:33:55Z') + ',some status example,,' + dateUtils.localizedDate('2023-09-12T09:33:58.544494Z') + ',,3.141' + + '\r\nname 1,,' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ',' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ',status name 1,,' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ',,123456789' + + '\r\nname 2,,' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ',' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ',status name 2,,' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ',,12345.6789' + + '\r\nname 3,,' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ',' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ',status name 3,,' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ',,7.1' const expectedFilename = 'some-test.csv' const mock = new ElementMock() @@ -273,11 +273,11 @@ describe('ExportDataService', () => { const expectedFilename = 'some-test.csv' const expectedHref = 'Name;Beschreibung;Startdatum;Enddatum;Status;Verantwortlich;Änderungsdatum;Erstellungsbenutzer;Testnummer' + - '\r\nsome name;;13. Sept. 2023, 11:34:05;14. Sept. 2023, 11:34:09;irgendein Status;someone responsible;12. Sept. 2023, 11:34:11;creation user;1' + - '\r\nexample;example description;12. Sept. 2023, 11:33:53;13. Sept. 2023, 11:33:55;irgendein Beispielstatus;;12. Sept. 2023, 11:33:58;;3.141' + - '\r\nname 1;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 1;;12. Sept. 2023, 11:34:27;;123456789' + - '\r\nname 2;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 2;;12. Sept. 2023, 11:34:27;;12345.6789' + - '\r\nname 3;;14. Sept. 2023, 11:34:22;15. Sept. 2023, 11:34:24;Status Name 3;;12. Sept. 2023, 11:34:27;;7.1' + '\r\nsome name;;' + dateUtils.localizedDate('2023-09-13T09:34:05Z') + ';' + dateUtils.localizedDate('2023-09-14T09:34:09Z') + ';irgendein Status;someone responsible;' + dateUtils.localizedDate('2023-09-12T09:34:11.997048Z') + ';creation user;1' + + '\r\nexample;example description;'+ dateUtils.localizedDate('2023-09-12T09:33:53Z') + ';' + dateUtils.localizedDate('2023-09-13T09:33:55Z') + ';irgendein Beispielstatus;;' + dateUtils.localizedDate('2023-09-12T09:33:58.544494Z') + ';;3.141' + + '\r\nname 1;;' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ';' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ';Status Name 1;;' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ';;123456789' + + '\r\nname 2;;' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ';' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ';Status Name 2;;' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ';;12345.6789' + + '\r\nname 3;;' + dateUtils.localizedDate('2023-09-14T09:34:22Z')+ ';' + dateUtils.localizedDate('2023-09-15T09:34:24Z') + ';Status Name 3;;' + dateUtils.localizedDate('2023-09-12T09:34:27.184086Z') + ';;7.1' const mock = new ElementMock() jest.spyOn(document, 'createElement').mockReturnValue(mock) From e55f9980fec962b4ecd94ff9082de591bdc4cc75 Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Wed, 31 Jan 2024 10:36:38 +0100 Subject: [PATCH 07/10] fix: use attr.aria-label to avoid cant bind since it isn't a known property --- .../data-list-grid/data-list-grid.component.html | 8 ++++---- .../core/components/data-table/data-table.component.html | 8 ++++---- .../components/search-header/search-header.component.html | 4 ++-- .../src/lib/core/portal-core.module.ts | 1 - 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/core/components/data-list-grid/data-list-grid.component.html b/libs/portal-integration-angular/src/lib/core/components/data-list-grid/data-list-grid.component.html index 33d67c8f..a4d42d21 100644 --- a/libs/portal-integration-angular/src/lib/core/components/data-list-grid/data-list-grid.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/data-list-grid/data-list-grid.component.html @@ -85,7 +85,7 @@ pButton class="p-button-rounded p-button-text mb-1 mr-2" title="{{ (viewMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.VIEW') | translate }}" - ariaLabel="{{ (viewMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.VIEW') | translate }}" + [attr.aria-label]="(viewMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.VIEW') | translate" (click)="onViewRow(item)" *ocxIfPermission="viewPermission" > @@ -98,7 +98,7 @@ icon="pi pi-pencil" pButton title="{{ (editMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.EDIT') | translate }}" - ariaLabel="{{ (editMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.EDIT') | translate }}" + [attr.aria-label]="(editMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.EDIT') | translate" (click)="onEditRow(item)" *ocxIfPermission="editPermission" > @@ -111,7 +111,7 @@ class="p-button-rounded p-button-text p-button-danger mb-1 mr-2" pButton title="{{ (deleteMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.DELETE') | translate }}" - ariaLabel="{{ (deleteMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.DELETE') | translate }}" + [attr.aria-label]="(deleteMenuItemKey || 'OCX_DATA_LIST_GRID.MENU.DELETE') | translate" (click)="onDeleteRow(item)" *ocxIfPermission="deletePermission" > @@ -125,7 +125,7 @@ [icon]="action.icon || ''" (click)="action.callback(item)" [title]="action.labelKey ? (action.labelKey | translate) : ''" - [ariaLabel]="action.labelKey ? (action.labelKey | translate) : ''" + [attr.aria-label]="action.labelKey ? (action.labelKey | translate) : ''" [disabled]="action.disabled" > diff --git a/libs/portal-integration-angular/src/lib/core/components/data-table/data-table.component.html b/libs/portal-integration-angular/src/lib/core/components/data-table/data-table.component.html index 6f1aceeb..0641a6bc 100644 --- a/libs/portal-integration-angular/src/lib/core/components/data-table/data-table.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/data-table/data-table.component.html @@ -91,7 +91,7 @@ pButton class="p-button-rounded p-button-text" title="{{ 'OCX_DATA_TABLE.ACTIONS.VIEW' | translate }}" - ariaLabel="{{ 'OCX_DATA_TABLE.ACTIONS.VIEW' | translate }}" + [attr.aria-label]="'OCX_DATA_TABLE.ACTIONS.VIEW' | translate" icon="pi pi-eye" (click)="onViewRow(rowObject)" > @@ -103,7 +103,7 @@ pButton class="p-button-rounded p-button-text" title="{{ 'OCX_DATA_TABLE.ACTIONS.EDIT' | translate }}" - ariaLabel="{{ 'OCX_DATA_TABLE.ACTIONS.EDIT' | translate }}" + [attr.aria-label]="'OCX_DATA_TABLE.ACTIONS.EDIT' | translate" icon="pi pi-pencil" (click)="onEditRow(rowObject)" > @@ -115,7 +115,7 @@ pButton class="p-button-rounded p-button-text p-button-danger" title="{{ 'OCX_DATA_TABLE.ACTIONS.DELETE' | translate }}" - ariaLabel="{{ 'OCX_DATA_TABLE.ACTIONS.DELETE' | translate }}" + [attr.aria-label]="'OCX_DATA_TABLE.ACTIONS.DELETE' | translate" icon="pi pi-trash" (click)="onDeleteRow(rowObject)" > @@ -129,7 +129,7 @@ [icon]="action.icon || ''" (click)="action.callback(rowObject)" [title]="action.labelKey ? (action.labelKey | translate) : ''" - [ariaLabel]="action.labelKey ? (action.labelKey | translate) : ''" + [attr.aria-label]="action.labelKey ? (action.labelKey | translate) : ''" [disabled]="action.disabled" > diff --git a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.html b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.html index ca8324c7..98f0abbb 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.html @@ -17,7 +17,7 @@ (onClick)="onResetClicked()" label="{{ 'OCX_SEARCH_HEADER.RESET_BUTTON_TEXT' | translate }}" icon="pi pi-eraser" - ariaLabel="{{ 'OCX_SEARCH_HEADER.RESET_BUTTON_TEXT' | translate }}" + [attr.aria-label]="'OCX_SEARCH_HEADER.RESET_BUTTON_TEXT' | translate" title="{{ 'OCX_SEARCH_HEADER.RESET_BUTTON_TEXT' | translate }}" > @@ -27,7 +27,7 @@ (onClick)="onSearchClicked()" label="{{ 'OCX_SEARCH_HEADER.SEARCH_BUTTON_TEXT' | translate }}" icon="pi pi-search" - ariaLabel="{{ 'OCX_SEARCH_HEADER.SEARCH_BUTTON_TEXT' | translate }}" + [attr.aria-label]="'OCX_SEARCH_HEADER.SEARCH_BUTTON_TEXT' | translate" title="{{ 'OCX_SEARCH_HEADER.SEARCH_BUTTON_TEXT' | translate }}" > diff --git a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts index 1b7a598d..9c90f3cf 100644 --- a/libs/portal-integration-angular/src/lib/core/portal-core.module.ts +++ b/libs/portal-integration-angular/src/lib/core/portal-core.module.ts @@ -167,7 +167,6 @@ export class PortalMissingTranslationHandler implements MissingTranslationHandle { provide: LOCALE_ID, useFactory: (userService: UserService) => { - console.log('Using locale: ' + userService.lang$.getValue()) return userService.lang$.getValue() }, deps: [UserService], From 767a1afd8489c7cf0c6581a01113f0657d36d705 Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Wed, 31 Jan 2024 11:34:55 +0100 Subject: [PATCH 08/10] fix: renaming, fix some other tests --- .../components/diagram/diagram.component.spec.ts | 1 + .../group-by-count-diagram.component.spec.ts | 1 + .../portal-header/header.component.spec.ts | 9 ++++++++- .../portal-viewport.component.spec.ts | 2 ++ .../search-header/search-header.component.spec.ts | 2 ++ .../src/lib/services/export-data-service.spec.ts | 12 ++++++------ .../src/lib/services/export-data.service.ts | 2 +- 7 files changed, 21 insertions(+), 8 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts index 2db91841..e9f6c34a 100644 --- a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts @@ -9,6 +9,7 @@ import { MockAuthModule } from '../../../mock-auth/mock-auth.module' import { DiagramHarness, TestbedHarnessEnvironment } from '../../../../../testing' import { TranslateService } from '@ngx-translate/core' import { DiagramType } from '../../../model/diagram-type' +import 'jest-canvas-mock'; describe('DiagramComponent', () => { let translateService: TranslateService diff --git a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts index 8203fdc3..be280f06 100644 --- a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts @@ -12,6 +12,7 @@ import { HarnessLoader } from '@angular/cdk/testing' import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' import { TranslateService } from '@ngx-translate/core' import { DiagramHarness } from '../../../../../testing' +import 'jest-canvas-mock'; describe('GroupByCountDiagramComponent', () => { let translateService: TranslateService diff --git a/libs/portal-integration-angular/src/lib/core/components/portal-header/header.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/portal-header/header.component.spec.ts index fa0b01ad..cb2b0b98 100644 --- a/libs/portal-integration-angular/src/lib/core/components/portal-header/header.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/portal-header/header.component.spec.ts @@ -9,6 +9,7 @@ import { ActivatedRoute, RouterModule } from '@angular/router' import { IfBreakpointDirective } from '../../directives/if-breakpoint.directive' import { TooltipModule } from 'primeng/tooltip' import { TranslateTestingModule } from 'ngx-translate-testing' +import { PortalCoreModule } from '../../portal-core.module' describe('HeaderComponent', () => { let component: HeaderComponent @@ -17,7 +18,13 @@ describe('HeaderComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [HeaderComponent, UserAvatarComponent, IfBreakpointDirective], - imports: [HttpClientTestingModule, RouterModule, TooltipModule, TranslateTestingModule.withTranslations({}),], + imports: [ + HttpClientTestingModule, + RouterModule, + TooltipModule, + TranslateTestingModule.withTranslations({}), + PortalCoreModule, + ], providers: [ { provide: AUTH_SERVICE, useClass: MockAuthService }, ConfigurationService, diff --git a/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts index 619fbe54..ae62e783 100644 --- a/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/portal-viewport/portal-viewport.component.spec.ts @@ -23,6 +23,7 @@ import { TranslateTestingModule } from 'ngx-translate-testing' import { AppStateService } from '../../../services/app-state.service' import { AUTH_SERVICE } from '../../../api/injection-tokens' import { MockAuthService } from '../../../mock-auth/mock-auth.service' +import { PortalCoreModule } from '../../portal-core.module' describe('PortalViewportComponent', () => { const origAddEventListener = window.addEventListener @@ -74,6 +75,7 @@ describe('PortalViewportComponent', () => { RouterModule, TooltipModule, TranslateTestingModule.withTranslations({}), + PortalCoreModule, ], providers: [ ConfigurationService, diff --git a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.spec.ts index d43f99f3..7b7ad4e0 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.spec.ts @@ -8,6 +8,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing' import { ButtonModule } from 'primeng/button' import { BreadcrumbModule } from 'primeng/breadcrumb' import { AppStateService } from '../../../services/app-state.service' +import { PortalCoreModule } from '../../portal-core.module' describe('SearchHeaderComponent', () => { const origAddEventListener = window.addEventListener @@ -44,6 +45,7 @@ describe('SearchHeaderComponent', () => { HttpClientTestingModule, ButtonModule, BreadcrumbModule, + PortalCoreModule ], providers: [ConfigurationService, AppStateService], }).compileComponents() diff --git a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts index 73ec95bb..5e999441 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data-service.spec.ts @@ -244,7 +244,7 @@ describe('ExportDataService', () => { ;(exportDataService).locale = 'en' ;(dateUtils).locale = 'en' - const expectedHref = + const expectedCsv = 'Name,Description,Start date,End date,Status,Responsible,Modification date,Creation user,Test number' + '\r\nsome name,,' + dateUtils.localizedDate('2023-09-13T09:34:05Z') + ',' + dateUtils.localizedDate('2023-09-14T09:34:09Z') + ',some status,someone responsible,' + dateUtils.localizedDate('2023-09-12T09:34:11.997048Z') + ',creation user,1' + '\r\nexample,example description,'+ dateUtils.localizedDate('2023-09-12T09:33:53Z') + ',' + dateUtils.localizedDate('2023-09-13T09:33:55Z') + ',some status example,,' + dateUtils.localizedDate('2023-09-12T09:33:58.544494Z') + ',,3.141' + @@ -259,9 +259,9 @@ describe('ExportDataService', () => { blobs.push(b) return (blobs.length - 1).toString() }) - await exportDataService.export(mockColumns, mockData, 'some-test.csv') + await exportDataService.exportCsv(mockColumns, mockData, 'some-test.csv') - expect(expectedHref).toEqual(await blobs[Number(mock.attributes['href'])].text()) + expect(expectedCsv).toEqual(await blobs[Number(mock.attributes['href'])].text()) expect(expectedFilename).toEqual(mock.attributes['download']) }) @@ -271,7 +271,7 @@ describe('ExportDataService', () => { ;(dateUtils).locale = 'de' const expectedFilename = 'some-test.csv' - const expectedHref = + const expectedCsv = 'Name;Beschreibung;Startdatum;Enddatum;Status;Verantwortlich;Änderungsdatum;Erstellungsbenutzer;Testnummer' + '\r\nsome name;;' + dateUtils.localizedDate('2023-09-13T09:34:05Z') + ';' + dateUtils.localizedDate('2023-09-14T09:34:09Z') + ';irgendein Status;someone responsible;' + dateUtils.localizedDate('2023-09-12T09:34:11.997048Z') + ';creation user;1' + '\r\nexample;example description;'+ dateUtils.localizedDate('2023-09-12T09:33:53Z') + ';' + dateUtils.localizedDate('2023-09-13T09:33:55Z') + ';irgendein Beispielstatus;;' + dateUtils.localizedDate('2023-09-12T09:33:58.544494Z') + ';;3.141' + @@ -285,9 +285,9 @@ describe('ExportDataService', () => { blobs.push(b) return (blobs.length - 1).toString() }) - await exportDataService.export(mockColumns, mockData, 'some-test.csv') + await exportDataService.exportCsv(mockColumns, mockData, 'some-test.csv') - expect(expectedHref).toEqual(await blobs[Number(mock.attributes['href'])].text()) + expect(expectedCsv).toEqual(await blobs[Number(mock.attributes['href'])].text()) expect(expectedFilename).toEqual(mock.attributes['download']) }) }) diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts index c720dc33..67ace8a6 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data.service.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -13,7 +13,7 @@ export class ExportDataService { @Inject(LOCALE_ID) private locale: string ) {} - async export( + async exportCsv( columns: { id: string; nameKey: string; columnType: ColumnType }[], data: Partial>[], fileName: string From 5137ba841438246788d869f13b27f9f9826d0c8f Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Wed, 31 Jan 2024 11:41:18 +0100 Subject: [PATCH 09/10] fix: sort imports --- .../diagram/diagram.component.spec.ts | 10 +++++----- .../group-by-count-diagram.component.spec.ts | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts index e9f6c34a..40fad278 100644 --- a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.spec.ts @@ -1,15 +1,15 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing' import { ComponentFixture, TestBed } from '@angular/core/testing' -import { DiagramComponent } from './diagram.component' import { NoopAnimationsModule } from '@angular/platform-browser/animations' +import { TranslateService } from '@ngx-translate/core' +import 'jest-canvas-mock' import { TranslateTestingModule } from 'ngx-translate-testing' -import { HttpClientTestingModule } from '@angular/common/http/testing' import { ChartModule } from 'primeng/chart' import { MessageModule } from 'primeng/message' -import { MockAuthModule } from '../../../mock-auth/mock-auth.module' import { DiagramHarness, TestbedHarnessEnvironment } from '../../../../../testing' -import { TranslateService } from '@ngx-translate/core' +import { MockAuthModule } from '../../../mock-auth/mock-auth.module' import { DiagramType } from '../../../model/diagram-type' -import 'jest-canvas-mock'; +import { DiagramComponent } from './diagram.component' describe('DiagramComponent', () => { let translateService: TranslateService diff --git a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts index be280f06..4fc6300f 100644 --- a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.spec.ts @@ -1,18 +1,18 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { GroupByCountDiagramComponent } from './group-by-count-diagram.component' +import { HarnessLoader } from '@angular/cdk/testing' +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' import { HttpClientTestingModule } from '@angular/common/http/testing' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { TranslateService } from '@ngx-translate/core' +import 'jest-canvas-mock' import { TranslateTestingModule } from 'ngx-translate-testing' -import { MockAuthModule } from '../../../mock-auth/mock-auth.module' -import { ColumnType } from '../../../model/column-type.model' -import { MessageModule } from 'primeng/message' import { ChartModule } from 'primeng/chart' -import { DiagramComponent } from '../diagram/diagram.component' +import { MessageModule } from 'primeng/message' import { firstValueFrom, of } from 'rxjs' -import { HarnessLoader } from '@angular/cdk/testing' -import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' -import { TranslateService } from '@ngx-translate/core' import { DiagramHarness } from '../../../../../testing' -import 'jest-canvas-mock'; +import { MockAuthModule } from '../../../mock-auth/mock-auth.module' +import { ColumnType } from '../../../model/column-type.model' +import { DiagramComponent } from '../diagram/diagram.component' +import { GroupByCountDiagramComponent } from './group-by-count-diagram.component' describe('GroupByCountDiagramComponent', () => { let translateService: TranslateService From 8c481b9a40f55564ac0fe0f414a44f71ac52132c Mon Sep 17 00:00:00 2001 From: "kim.tran" Date: Thu, 1 Feb 2024 16:02:49 +0100 Subject: [PATCH 10/10] fix: if columns empty then just return --- .../src/lib/services/export-data.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/portal-integration-angular/src/lib/services/export-data.service.ts b/libs/portal-integration-angular/src/lib/services/export-data.service.ts index 67ace8a6..b256f900 100644 --- a/libs/portal-integration-angular/src/lib/services/export-data.service.ts +++ b/libs/portal-integration-angular/src/lib/services/export-data.service.ts @@ -18,6 +18,9 @@ export class ExportDataService { data: Partial>[], fileName: string ): Promise { + if (!columns.length ) { + return + } const flattenedData = data.map((d) => columns.reduce((obj, c) => ({ ...obj, [c.id]: ObjectUtils.resolveFieldData(d, c.id) }), {}) )