From 635339aae8dedfa96d6a77798de591ef4ae49829 Mon Sep 17 00:00:00 2001 From: Annika Nowak <139357202+anninowak@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:14:39 +0100 Subject: [PATCH 1/3] feat: save and edit search config component (#139) development of a ui component for saving and editing search configs --- .../assets/i18n/de.json | 5 + .../assets/i18n/en.json | 5 + libs/portal-integration-angular/src/index.ts | 1 + ...r-edit-search-config-dialog.component.html | 27 ++++ ...r-edit-search-config-dialog.component.scss | 14 ++ ...dit-search-config-dialog.component.spec.ts | 147 ++++++++++++++++++ ...-or-edit-search-config-dialog.component.ts | 59 +++++++ .../interactive-data-view.component.ts | 3 +- .../search-config.component.spec.ts | 12 +- .../search-config/search-config.component.ts | 6 +- .../search-header.component.html | 3 +- .../search-header/search-header.component.ts | 12 +- .../src/lib/core/portal-core.module.ts | 3 + .../src/lib/core/primeng.module.ts | 3 + ...te-or-edit-search-config-dialog.harness.ts | 19 +++ .../testing/div.harness.ts | 10 +- .../testing/index.ts | 2 + .../testing/input.harness.ts | 7 + .../testing/primeng/p-checkbox.harness.ts | 35 +++++ 19 files changed, 354 insertions(+), 19 deletions(-) create mode 100644 libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.html create mode 100644 libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.scss create mode 100644 libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.spec.ts create mode 100644 libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.ts create mode 100644 libs/portal-integration-angular/testing/create-or-edit-search-config-dialog.harness.ts create mode 100644 libs/portal-integration-angular/testing/primeng/p-checkbox.harness.ts diff --git a/libs/portal-integration-angular/assets/i18n/de.json b/libs/portal-integration-angular/assets/i18n/de.json index 6700ddf1..49f81244 100644 --- a/libs/portal-integration-angular/assets/i18n/de.json +++ b/libs/portal-integration-angular/assets/i18n/de.json @@ -16,6 +16,11 @@ "RIGHT": "Rechts" } }, + "OCX_SEARCH_CONFIG": { + "PLACEHOLDER": "Bitte geben Sie den Namen der Suchkonfiguration an.", + "SAVE_COLUMNS_OPTION": "Möchten Sie die angezeigten Spalten speichern?", + "SAVE_INPUT_FIELDS_OPTION": "Möchten Sie die Werte aus den Suchfeldern speichern?" + }, "OCX_LIST_GRID_SORT": { "DROPDOWN": { "PLACEHOLDER": "Sortierung auswählen", diff --git a/libs/portal-integration-angular/assets/i18n/en.json b/libs/portal-integration-angular/assets/i18n/en.json index 357b85a7..1ec7f442 100644 --- a/libs/portal-integration-angular/assets/i18n/en.json +++ b/libs/portal-integration-angular/assets/i18n/en.json @@ -16,6 +16,11 @@ "RIGHT": "Right" } }, + "OCX_SEARCH_CONFIG": { + "PLACEHOLDER": "Please enter the name of the search configuration", + "SAVE_COLUMNS_OPTION": "Do you want to save the shown columns?", + "SAVE_INPUT_FIELDS_OPTION": "Do you want to save the input values from the input fields?" + }, "OCX_LIST_GRID_SORT": { "DROPDOWN": { "PLACEHOLDER": "Select sorting", diff --git a/libs/portal-integration-angular/src/index.ts b/libs/portal-integration-angular/src/index.ts index bde66c78..e4189909 100644 --- a/libs/portal-integration-angular/src/index.ts +++ b/libs/portal-integration-angular/src/index.ts @@ -56,6 +56,7 @@ export * from './lib/core/components/search-config/search-config.component' export * from './lib/core/components/loading-indicator/loading-indicator.component' export * from './lib/core/components/content-container/content-container.component' export * from './lib/core/components/content/content.component' +export * from './lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component' // services export * from './lib/services/app.menu.service' diff --git a/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.html b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.html new file mode 100644 index 00000000..e8b4d141 --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.html @@ -0,0 +1,27 @@ +
+
+ +
+ +
+ + + +
+ +
+ + +
+
diff --git a/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.scss b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.scss new file mode 100644 index 00000000..a530b42b --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.scss @@ -0,0 +1,14 @@ +.searchConfigDialog { + display: flex; + flex-direction: column; + gap: 1em; + margin-bottom: 1em; +} + +:host ::ng-deep .p-inputtext { + width: 100%; +} + +:host ::ng-deep .p-checkbox { + margin-right: 1em; +} diff --git a/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.spec.ts new file mode 100644 index 00000000..077467fe --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.spec.ts @@ -0,0 +1,147 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { CreateOrEditSearchConfigDialogComponent } from './create-or-edit-search-config-dialog.component' +import { CheckboxModule } from 'primeng/checkbox' +import { MockAuthModule } from '../../../mock-auth/mock-auth.module' +import { TranslateTestingModule } from 'ngx-translate-testing' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { TranslateService } from '@ngx-translate/core' +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' +import { PCheckboxHarness, CreateOrEditSearchConfigDialogHarness } from '../../../../../testing' +import { DialogState } from '../../../services/portal-dialog.service' +import { ReactiveFormsModule } from '@angular/forms' +import { InputTextModule } from 'primeng/inputtext' + +describe('CreateOrEditSearchConfigDialogComponent', () => { + let component: CreateOrEditSearchConfigDialogComponent + let fixture: ComponentFixture + let translateService: TranslateService + let dialogHarness: CreateOrEditSearchConfigDialogHarness + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CreateOrEditSearchConfigDialogComponent], + imports: [ + CheckboxModule, + MockAuthModule, + TranslateTestingModule.withTranslations({ + en: require('./../../../../../assets/i18n/en.json'), + de: require('./../../../../../assets/i18n/de.json'), + }), + HttpClientTestingModule, + ReactiveFormsModule, + InputTextModule, + ], + }).compileComponents() + + fixture = TestBed.createComponent(CreateOrEditSearchConfigDialogComponent) + component = fixture.componentInstance + + translateService = TestBed.inject(TranslateService) + translateService.setDefaultLang('en') + translateService.use('en') + + fixture.detectChanges() + dialogHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, CreateOrEditSearchConfigDialogHarness) + }) + + it('should create the component', () => { + expect(component).toBeTruthy() + }) + + it('should load the CreateOrEditSearchConfigDialogHarness', async () => { + expect(dialogHarness).toBeTruthy() + }) + + it('should set the DialogResult of the saveInputValuesId checkbox to true when the saveInputValuesId checkbox is checked', async () => { + const saveInputValuesCheckbox = await dialogHarness.getHarness( + PCheckboxHarness.with({ inputid: 'saveInputValuesId' }) + ) + await saveInputValuesCheckbox.click() + const _state: DialogState = { button: 'primary', result: undefined } + component.ocxDialogButtonClicked(_state) + const dialogResult = { + searchConfigName: '', + saveInputValues: true, + saveColumns: false, + } + expect(component.dialogResult).toEqual(dialogResult) + }) + + it('should set the DialogResult of the saveColumnsId checkbox initially false', async () => { + const _state: DialogState = { button: 'primary', result: undefined } + await component.ocxDialogButtonClicked(_state) + const dialogResult = { + searchConfigName: '', + saveInputValues: false, + saveColumns: false, + } + expect(component.dialogResult).toEqual(dialogResult) + }) + + it('should set the DialogResult of the searchConfig input Field to the entered value', async () => { + await (await dialogHarness.getSearchConfigInputHarness()).setValue('search Config') + const _state: DialogState = { button: 'primary', result: undefined } + await component.ocxDialogButtonClicked(_state) + const dialogResult = { + searchConfigName: 'search Config', + saveInputValues: false, + saveColumns: false, + } + expect(component.dialogResult).toEqual(dialogResult) + }) + + it('should set the saveColumnsId checkbox initially to unchecked', async () => { + const saveInputValuesCheckbox = await dialogHarness.getSaveColumnsCheckboxHarness() + const checked = await saveInputValuesCheckbox.isChecked() + expect(checked).toBeFalsy() + }) + + it('should set the saveInputValues checkbox initially to unchecked', async () => { + const saveInputValuesCheckbox = await dialogHarness.getSaveInputValuesCheckboxHarness() + const checked = await saveInputValuesCheckbox.isChecked() + expect(checked).toBeFalsy() + }) + + it('should set the saveInputValues checkbox to true when it is clicked', async () => { + const saveInputValuesCheckbox = await dialogHarness.getSaveInputValuesCheckboxHarness() + await saveInputValuesCheckbox.click() + const checked = await saveInputValuesCheckbox.isChecked() + expect(checked).toBeTruthy() + }) + + it('should emit true when the searchConfig name is not an empty string and the saveColumnsCheckBox is clicked', async () => { + let done: () => void + const finished = new Promise((resolve) => (done = resolve)) + let enabled = false + component.primaryButtonEnabled.subscribe((v) => { + enabled = v + done() + }) + + const searchConfigInputHarness = await dialogHarness.getSearchConfigInputHarness() + searchConfigInputHarness.setValue('test') + const saveInputValuesCheckbox = await dialogHarness.getSaveColumnsCheckboxHarness() + await saveInputValuesCheckbox.click() + + await finished + expect(enabled).toEqual(true) + }) + + it('emit true when the searchConfig Name is not an empty string and the saveInputValuesCheckbox is clicked', async () => { + let done: () => void + const finished = new Promise((resolve) => (done = resolve)) + let enabled = false + component.primaryButtonEnabled.subscribe((v) => { + enabled = v + done() + }) + + const searchConfigInputHarness = await dialogHarness.getSearchConfigInputHarness() + searchConfigInputHarness.setValue('test') + const saveInputValuesCheckbox = await dialogHarness.getSaveInputValuesCheckboxHarness() + await saveInputValuesCheckbox.click() + + await finished + expect(enabled).toEqual(true) + }) +}) diff --git a/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.ts b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.ts new file mode 100644 index 00000000..b0205c84 --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component.ts @@ -0,0 +1,59 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' +import { FormControl, FormGroup } from '@angular/forms' +import { + DialogButtonClicked, + DialogPrimaryButtonDisabled, + DialogResult, + DialogState, +} from '../../../services/portal-dialog.service' +import { Observable, map } from 'rxjs' + +export type CreateOrEditSearchDialogContent = { + searchConfigName: string + saveInputValues: boolean + saveColumns: boolean +} +@Component({ + selector: 'ocx-create-or-edit-search-config-dialog', + templateUrl: './create-or-edit-search-config-dialog.component.html', + styleUrls: ['./create-or-edit-search-config-dialog.component.scss'], +}) +export class CreateOrEditSearchConfigDialogComponent + implements + DialogPrimaryButtonDisabled, + DialogResult, + DialogButtonClicked +{ + @Input() searchConfigName: string | undefined + @Input() saveInputValues: boolean | undefined + @Input() saveColumns: boolean | undefined + placeHolderKey = 'OCX_SEARCH_CONFIG.PLACEHOLDER' + @Output() primaryButtonEnabled: EventEmitter = new EventEmitter() + searchConfigFormGroup: FormGroup = new FormGroup({ + searchConfigName: new FormControl(''), + saveInputValues: new FormControl(false), + saveColumns: new FormControl(false), + }) + dialogResult: CreateOrEditSearchDialogContent = { searchConfigName: '', saveInputValues: false, saveColumns: false } + constructor() { + this.searchConfigFormGroup.valueChanges + .pipe( + map( + (dialogFormValues: CreateOrEditSearchDialogContent) => + !!dialogFormValues.searchConfigName && (dialogFormValues.saveInputValues || dialogFormValues.saveColumns) + ) + ) + .subscribe(this.primaryButtonEnabled) + } + + ocxDialogButtonClicked( + _state: DialogState + ): boolean | Observable | Promise | undefined { + this.dialogResult = { + searchConfigName: this.searchConfigFormGroup?.get('searchConfigName')?.value, + saveInputValues: this.searchConfigFormGroup?.get('saveInputValues')?.value, + saveColumns: this.searchConfigFormGroup?.get('saveColumns')?.value, + } + return true + } +} 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 a36073b9..65cbb6e8 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 @@ -59,6 +59,7 @@ export class InteractiveDataViewComponent implements OnInit { @Input() tablePaginator = true @Input() page = 0 @Input() selectedRows: Row[] = [] + @Input() displayedColumns: DataTableColumn[] = [] @ContentChild('tableCell') tableCell: TemplateRef | undefined @ContentChild('tableDateCell') tableDateCell: TemplateRef | undefined @ContentChild('tableRelativeDateCell') tableRelativeDateCell: TemplateRef | undefined @@ -78,8 +79,8 @@ export class InteractiveDataViewComponent implements OnInit { @Output() dataViewLayoutChange = new EventEmitter<'grid' | 'list' | 'table'>() @Output() displayedColumnsChange = new EventEmitter() @Output() selectionChanged: EventEmitter = new EventEmitter() + @Output() pageChanged: EventEmitter = new EventEmitter() - displayedColumns: DataTableColumn[] = [] selectedGroupKey = '' isDeleteItemObserved: boolean | undefined isViewItemObserved: boolean | undefined diff --git a/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.spec.ts index 229285ea..50de2329 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.spec.ts @@ -30,7 +30,7 @@ describe('SearchConfigComponent', () => { }, ] - const searchConfigsEntries: SearchConfig[] = [ + const searchConfigs: SearchConfig[] = [ { id: '01', name: 'Basic search config', @@ -82,7 +82,7 @@ describe('SearchConfigComponent', () => { fixture = TestBed.createComponent(SearchConfigComponent) component = fixture.componentInstance - component.searchConfigs = searchConfigsEntries + component.searchConfigs = searchConfigs translateService = TestBed.inject(TranslateService) translateService.setDefaultLang('en') translateService.use('en') @@ -110,7 +110,7 @@ describe('SearchConfigComponent', () => { const searchConfigHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, SearchConfigHarness) const dropdown = await searchConfigHarness.getSearchConfigDropdown() const items = await dropdown?.getDropdownItems() - expect(items?.length).toEqual(searchConfigsEntries.length) + expect(items?.length).toEqual(searchConfigs.length) }) it('should display no dropdown if the search config is empty', async () => { @@ -124,14 +124,14 @@ describe('SearchConfigComponent', () => { const searchConfigHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, SearchConfigHarness) const dropdown = await searchConfigHarness.getSearchConfigDropdown() const selectedDropdownItem = await dropdown?.selectedDropdownItemText(0) - expect(selectedDropdownItem).toEqual(searchConfigsEntries[0].name) + expect(selectedDropdownItem).toEqual(searchConfigs[0].name) }) it('should display the values in the fields after selecting the fist hard coded search config', async () => { const searchConfigHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, SearchConfigHarness) const dropdown = await searchConfigHarness.getSearchConfigDropdown() const selectedDropdownItem = await dropdown?.selectedDropdownItemText(1) - expect(selectedDropdownItem).toEqual(searchConfigsEntries[1].name) + expect(selectedDropdownItem).toEqual(searchConfigs[1].name) }) it('should display the values in the fields correctly after selecting the fist search config and then selecting the second search config', async () => { @@ -139,7 +139,7 @@ describe('SearchConfigComponent', () => { const dropdown = await searchConfigHarness.getSearchConfigDropdown() let selectedDropdownItem = await dropdown?.selectedDropdownItemText(0) selectedDropdownItem = await dropdown?.selectedDropdownItemText(1) - expect(selectedDropdownItem).toEqual(searchConfigsEntries[1].name) + expect(selectedDropdownItem).toEqual(searchConfigs[1].name) }) it('should have the option to remove the selection', async () => { diff --git a/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.ts b/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.ts index 671b63dc..884af52c 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/search-config/search-config.component.ts @@ -14,7 +14,7 @@ export class SearchConfigComponent implements OnInit { @Input() placeholderKey = 'OCX_SEARCH_HEADER.OCX_SEARCH_CONFIG.DROPDOWN_DEFAULT' @Output() - selectedSearchConfig: EventEmitter = new EventEmitter() + selectedSearchConfigChanged: EventEmitter = new EventEmitter() formGroup: FormGroup | undefined ngOnInit(): void { @@ -23,7 +23,7 @@ export class SearchConfigComponent implements OnInit { }) } - onSearchConfigChange(event: SearchConfig[]) { - this.selectedSearchConfig?.emit(event) + onSearchConfigChange(searchConfig: SearchConfig) { + this.selectedSearchConfigChanged?.emit(searchConfig) } } 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 98f0abbb..5b0ce2c0 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 @@ -4,7 +4,8 @@ [actions]="headerActions" > - + +
diff --git a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.ts b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.ts index 0680ddd0..9d2d445d 100644 --- a/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/search-header/search-header.component.ts @@ -24,8 +24,9 @@ import { SearchConfig } from '../../../model/search-config' styleUrls: ['./search-header.component.scss'], }) export class SearchHeaderComponent implements AfterViewInit { - @Input() searchConfigsEntries: SearchConfig[] | undefined + @Input() searchConfigs: SearchConfig[] | undefined @Input() headline = '' + @Input() viewMode: 'basic' | 'advanced' = 'basic' @Input() manualBreadcrumbs = false _actions: Action[] = [] @Input() @@ -39,7 +40,8 @@ export class SearchHeaderComponent implements AfterViewInit { @Output() searched: EventEmitter = new EventEmitter() @Output() resetted: EventEmitter = new EventEmitter() - @Output() selectedSearchConfig: EventEmitter = new EventEmitter() + @Output() selectedSearchConfigChanged: EventEmitter = new EventEmitter() + @Output() viewModeChanged: EventEmitter = new EventEmitter() @ContentChild('additionalToolbarContent') additionalToolbarContent: TemplateRef | undefined @@ -49,7 +51,6 @@ export class SearchHeaderComponent implements AfterViewInit { @ViewChild('searchParameterFields') searchParameterFields: ElementRef | undefined - viewMode: 'basic' | 'advanced' = 'basic' hasAdvanced = false headerActions: Action[] = [] @@ -59,6 +60,7 @@ export class SearchHeaderComponent implements AfterViewInit { toggleViewMode() { this.viewMode = this.viewMode === 'basic' ? 'advanced' : 'basic' + this.viewModeChanged?.emit(this.viewMode) this.updateHeaderActions() setTimeout(() => this.addKeyUpEventListener()) } @@ -103,7 +105,7 @@ export class SearchHeaderComponent implements AfterViewInit { } } - confirmSearchConfig(event: any) { - this.selectedSearchConfig?.emit(event) + confirmSearchConfig(searchConfig: SearchConfig) { + this.selectedSearchConfigChanged?.emit(searchConfig) } } 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 98e64f1c..393b6a49 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 @@ -86,6 +86,7 @@ import { UserProfileAPIService } from '../services/userprofile-api.service' import { createTranslateLoader } from './utils/create-translate-loader.utils' import { MessageService } from 'primeng/api' import { TranslationCacheService } from '../services/translation-cache.service' +import { CreateOrEditSearchConfigDialogComponent } from './components/create-or-edit-search-config-dialog/create-or-edit-search-config-dialog.component' export class PortalMissingTranslationHandler implements MissingTranslationHandler { handle(params: MissingTranslationHandlerParams) { @@ -167,6 +168,7 @@ export class PortalMissingTranslationHandler implements MissingTranslationHandle OcxContentComponent, OcxContentContainerComponent, SearchConfigComponent, + CreateOrEditSearchConfigDialogComponent, ], providers: [ { @@ -237,6 +239,7 @@ export class PortalMissingTranslationHandler implements MissingTranslationHandle OcxContentComponent, OcxContentContainerComponent, SearchConfigComponent, + CreateOrEditSearchConfigDialogComponent, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], entryComponents: [ColumnTogglerComponent], diff --git a/libs/portal-integration-angular/src/lib/core/primeng.module.ts b/libs/portal-integration-angular/src/lib/core/primeng.module.ts index ade47c52..ec99f0a7 100644 --- a/libs/portal-integration-angular/src/lib/core/primeng.module.ts +++ b/libs/portal-integration-angular/src/lib/core/primeng.module.ts @@ -22,6 +22,7 @@ import { DialogModule } from 'primeng/dialog' import { InputSwitchModule } from 'primeng/inputswitch' import { DataViewModule } from 'primeng/dataview' import { ChartModule } from 'primeng/chart' +import { CheckboxModule } from 'primeng/checkbox' @NgModule({ imports: [ @@ -49,6 +50,7 @@ import { ChartModule } from 'primeng/chart' DataViewModule, ChartModule, MessageModule, + CheckboxModule, ], exports: [ BadgeModule, @@ -75,6 +77,7 @@ import { ChartModule } from 'primeng/chart' DataViewModule, ChartModule, MessageModule, + CheckboxModule, ], }) export class PrimeNgModule {} diff --git a/libs/portal-integration-angular/testing/create-or-edit-search-config-dialog.harness.ts b/libs/portal-integration-angular/testing/create-or-edit-search-config-dialog.harness.ts new file mode 100644 index 00000000..d056751b --- /dev/null +++ b/libs/portal-integration-angular/testing/create-or-edit-search-config-dialog.harness.ts @@ -0,0 +1,19 @@ +import { ContentContainerComponentHarness } from '@angular/cdk/testing' +import { PCheckboxHarness } from './primeng/p-checkbox.harness' +import { InputHarness } from './input.harness' + +export class CreateOrEditSearchConfigDialogHarness extends ContentContainerComponentHarness { + static hostSelector = 'ocx-create-or-edit-search-config-dialog' + + getSaveInputValuesCheckboxHarness() { + return this.getHarness(PCheckboxHarness.with({ inputid: 'saveInputValuesId' })) + } + + getSaveColumnsCheckboxHarness() { + return this.getHarness(PCheckboxHarness.with({ inputid: 'saveColumnsId' })) + } + + getSearchConfigInputHarness() { + return this.getHarness(InputHarness.with({ id: 'searchConfigName' })) + } +} diff --git a/libs/portal-integration-angular/testing/div.harness.ts b/libs/portal-integration-angular/testing/div.harness.ts index 39669eae..6cf90005 100644 --- a/libs/portal-integration-angular/testing/div.harness.ts +++ b/libs/portal-integration-angular/testing/div.harness.ts @@ -27,10 +27,14 @@ export class DivHarness extends ComponentHarness { async getClassList() { const host = await this.host() - const attributeString = await host.getAttribute("class"); - if(attributeString) { - return attributeString.trim().split(" ") + const attributeString = await host.getAttribute('class') + if (attributeString) { + return attributeString.trim().split(' ') } return [] } + + async click(): Promise { + await (await this.host()).click() + } } diff --git a/libs/portal-integration-angular/testing/index.ts b/libs/portal-integration-angular/testing/index.ts index 4ff94656..d2744661 100644 --- a/libs/portal-integration-angular/testing/index.ts +++ b/libs/portal-integration-angular/testing/index.ts @@ -8,6 +8,7 @@ export * from './primeng/p-multiSelectListItem.harness' export * from './primeng/p-picklist.harness' export * from './primeng/p-selectButton.harness' export * from './primeng/p-paginator.harness' +export * from './primeng/p-checkbox.harness' export * from './button-dialog.harness' export * from './button.harness' @@ -33,6 +34,7 @@ export * from './search-config.harness' export * from './span.harness' export * from './page-header.harness' export * from './p-tableCheckbox.harness' +export * from './create-or-edit-search-config-dialog.harness' export * from '@angular/cdk/testing' export * from '@angular/cdk/testing/testbed' diff --git a/libs/portal-integration-angular/testing/input.harness.ts b/libs/portal-integration-angular/testing/input.harness.ts index 32cab9d5..834eb479 100644 --- a/libs/portal-integration-angular/testing/input.harness.ts +++ b/libs/portal-integration-angular/testing/input.harness.ts @@ -20,6 +20,9 @@ export class InputHarness extends ComponentHarness { async getValue(): Promise { return await (await this.host()).getProperty('value') } + async getChecked(): Promise { + return await (await this.host()).getProperty('checked') + } async setValue(value: string | Date): Promise { if (value instanceof Date) { @@ -33,6 +36,10 @@ export class InputHarness extends ComponentHarness { })} ${value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` ) } else { + await (await this.host()).clear() + if (value) { + await (await this.host()).sendKeys(value) + } await (await this.host()).setInputValue(value) } } diff --git a/libs/portal-integration-angular/testing/primeng/p-checkbox.harness.ts b/libs/portal-integration-angular/testing/primeng/p-checkbox.harness.ts new file mode 100644 index 00000000..5d435ac4 --- /dev/null +++ b/libs/portal-integration-angular/testing/primeng/p-checkbox.harness.ts @@ -0,0 +1,35 @@ +import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing' +import { DivHarness } from '../div.harness' +import { InputHarness } from '../input.harness' + +export interface PCheckBoxHarnessFilters extends BaseHarnessFilters { + inputid?: string +} +export class PCheckboxHarness extends ComponentHarness { + static hostSelector = 'p-checkbox' + + getCheckBoxDiv = this.locatorForOptional(DivHarness.with({ class: 'p-checkbox-box' })) + + static with(options: PCheckBoxHarnessFilters): HarnessPredicate { + return new HarnessPredicate(PCheckboxHarness, options).addOption('inputid', options.inputid, (harness, inputid) => + HarnessPredicate.stringMatches(harness.getId(), inputid) + ) + } + + async isChecked(): Promise { + return (await this.locatorFor(InputHarness)()).getChecked() + } + + async getId(): Promise { + return await (await this.host()).getAttribute('inputId') + } + + async isHidden(): Promise { + const attr = await (await this.host()).getAttribute('hidden') + return Boolean(attr) + } + + async click(): Promise { + await (await this.getCheckBoxDiv())?.click() + } +} From ac0e2e051f7dee12e45561bfda1cebf642d0504c Mon Sep 17 00:00:00 2001 From: Bastian Jakobi <55296998+bastianjakobi@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:48:04 +0100 Subject: [PATCH 2/3] feat: allow dynamically switching diagram type (#144) * feat: implement diagramType select button * test: add tests for diagramComponent * test: add smoke tests for groupByCountDiagramComponent * fix: add meaningful titles * test: adjust tests to be compliant with latest changes * fix: fix button titles * fix: fix button titles --- .../assets/i18n/de.json | 7 +- .../assets/i18n/en.json | 7 +- .../components/diagram/diagram.component.html | 13 ++ .../components/diagram/diagram.component.scss | 9 ++ .../diagram/diagram.component.spec.ts | 113 +++++++++++++++- .../diagram/diagram.component.stories.ts | 91 +++++++++++++ .../components/diagram/diagram.component.ts | 36 ++++- .../group-by-count-diagram.component.html | 9 +- .../group-by-count-diagram.component.spec.ts | 19 +++ ...roup-by-count-diagram.component.stories.ts | 124 ++++++++++++++++++ .../group-by-count-diagram.component.ts | 19 ++- .../testing/diagram.harness.ts | 9 ++ 12 files changed, 450 insertions(+), 6 deletions(-) create mode 100644 libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.scss create mode 100644 libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.stories.ts create mode 100644 libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.stories.ts diff --git a/libs/portal-integration-angular/assets/i18n/de.json b/libs/portal-integration-angular/assets/i18n/de.json index 49f81244..73abc291 100644 --- a/libs/portal-integration-angular/assets/i18n/de.json +++ b/libs/portal-integration-angular/assets/i18n/de.json @@ -88,7 +88,12 @@ }, "OCX_DIAGRAM": { "SUM": "Gesamtanzahl", - "NO_DATA": "Es sind keine Daten vorhanden" + "NO_DATA": "Es sind keine Daten vorhanden", + "SWITCH_DIAGRAM_TYPE": { + "PIE": "Zu Tortendiagramm wechseln", + "HORIZONTAL_BAR": "Zu horizontalem Balkendiagramm wechseln", + "VERTICAL_BAR": "Zu vertikalem Balkendiagramm wechseln" + } }, "OCX_PORTAL_VIEWPORT": { "SUCCESS": "Erfolg!", diff --git a/libs/portal-integration-angular/assets/i18n/en.json b/libs/portal-integration-angular/assets/i18n/en.json index 1ec7f442..8a1165cf 100644 --- a/libs/portal-integration-angular/assets/i18n/en.json +++ b/libs/portal-integration-angular/assets/i18n/en.json @@ -88,7 +88,12 @@ }, "OCX_DIAGRAM": { "SUM": "Total", - "NO_DATA": "There is no data available" + "NO_DATA": "There is no data available", + "SWITCH_DIAGRAM_TYPE": { + "PIE": "Switch to pie chart", + "HORIZONTAL_BAR": "Switch to horizontal bar chart", + "VERTICAL_BAR": "Switch to vertical bar chart" + } }, "OCX_PORTAL_VIEWPORT": { "SUCCESS": "Success!", diff --git a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.html b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.html index 69a3f032..ca850eb0 100644 --- a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.html @@ -1,4 +1,17 @@ +
+ + + + + +
{ let translateService: TranslateService @@ -38,6 +41,8 @@ describe('DiagramComponent', () => { ChartModule, MessageModule, MockAuthModule, + SelectButtonModule, + FormsModule, TranslateTestingModule.withTranslations({ en: require('./../../../../../assets/i18n/en.json'), de: require('./../../../../../assets/i18n/de.json'), @@ -103,4 +108,110 @@ describe('DiagramComponent', () => { const chartType = await chartHarness.getType() expect(chartType).toEqual('bar') }) + + it('should not display a diagramType select button by default', async () => { + expect(component.supportedDiagramTypes).toEqual([]) + expect(component.shownDiagramTypes).toEqual([]) + + const diagram = await TestbedHarnessEnvironment.harnessForFixture(fixture, DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + + expect(diagramTypeSelectButton).toBe(null) + }) + + it('should render a diagramType select button if supportedDiagramTypes is specified', async () => { + const expectedDiagramLayouts: DiagramLayouts[] = [ + { icon: PrimeIcons.CHART_PIE, layout: DiagramType.PIE, titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.PIE' }, + { + icon: PrimeIcons.BARS, + layout: DiagramType.HORIZONTAL_BAR, + titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.HORIZONTAL_BAR', + }, + ] + + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR] + const diagram = await TestbedHarnessEnvironment.harnessForFixture(fixture, DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + const diagramTypeSelectButtonOptions = await diagram.getAllSelectionButtons() + + expect(component.shownDiagramTypes).toEqual(expectedDiagramLayouts) + expect(diagramTypeSelectButton).toBeTruthy() + expect(diagramTypeSelectButtonOptions.length).toBe(2) + }) + + it('should change the rendered diagram whenever the select button is used to change the diagramType', async () => { + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR] + + const diagram = await TestbedHarnessEnvironment.harnessForFixture(fixture, DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + const diagramTypeSelectButtonOptions = await diagram.getAllSelectionButtons() + + let diagramTypeChangedEvent: DiagramType | undefined + component.diagramTypeChanged.subscribe((event) => (diagramTypeChangedEvent = event)) + + expect(diagramTypeSelectButton).toBeTruthy() + expect(component.diagramType).toBe(DiagramType.PIE) + let chartHarness = await diagram.getChart() + let chartType = await chartHarness.getType() + expect(chartType).toEqual('pie') + + await diagramTypeSelectButtonOptions[1].click() + expect(component.diagramType).toBe(DiagramType.HORIZONTAL_BAR) + chartHarness = await diagram.getChart() + chartType = await chartHarness.getType() + expect(chartType).toEqual('bar') + expect(diagramTypeChangedEvent).toBe(DiagramType.HORIZONTAL_BAR) + + await diagramTypeSelectButtonOptions[0].click() + expect(component.diagramType).toBe(DiagramType.PIE) + chartHarness = await diagram.getChart() + chartType = await chartHarness.getType() + expect(chartType).toEqual('pie') + expect(diagramTypeChangedEvent).toBe(DiagramType.PIE) + }) + + it('should dynamically add/remove options to/from the diagramType select button', async () => { + const allDiagramLayouts: DiagramLayouts[] = [ + { icon: PrimeIcons.CHART_PIE, layout: DiagramType.PIE, titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.PIE' }, + { + icon: PrimeIcons.BARS, + layout: DiagramType.HORIZONTAL_BAR, + titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.HORIZONTAL_BAR', + }, + { + icon: PrimeIcons.CHART_BAR, + layout: DiagramType.VERTICAL_BAR, + titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.VERTICAL_BAR', + }, + ] + + expect(component.shownDiagramTypes).toEqual([]) + + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR] + const diagram = await TestbedHarnessEnvironment.harnessForFixture(fixture, DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + + expect(diagramTypeSelectButton).toBeTruthy() + expect(component.shownDiagramTypes).toEqual(allDiagramLayouts.slice(0, 2)) + const diagramTypeSelectButtonOptions = await diagram.getAllSelectionButtons() + expect(diagramTypeSelectButtonOptions.length).toBe(2) + + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR] + const diagramTypeSelectButtonAfterUpdate = await diagram.getDiagramTypeSelectButton() + const diagramTypeSelectButtonOptionsAfterUpdate = await diagram.getAllSelectionButtons() + expect(diagramTypeSelectButtonAfterUpdate).toBeTruthy() + expect(component.shownDiagramTypes).toEqual(allDiagramLayouts) + expect(diagramTypeSelectButtonOptionsAfterUpdate.length).toBe(3) + }) + + it('should automatically select the button for the currently displayed diagram', async () => { + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR] + component.diagramType = DiagramType.HORIZONTAL_BAR + + const diagram = await TestbedHarnessEnvironment.harnessForFixture(fixture, DiagramHarness) + const diagramTypeSelectButtonOptions = await diagram.getAllSelectionButtons() + + expect(await diagramTypeSelectButtonOptions[0].hasClass('p-highlight')).toBe(false) + expect(await diagramTypeSelectButtonOptions[1].hasClass('p-highlight')).toBe(true) + }) }) diff --git a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.stories.ts b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.stories.ts new file mode 100644 index 00000000..d1b3f86f --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.stories.ts @@ -0,0 +1,91 @@ +import { importProvidersFrom } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { Meta, StoryFn, applicationConfig, moduleMetadata } from '@storybook/angular' +import { BreadcrumbModule } from 'primeng/breadcrumb' +import { ButtonModule } from 'primeng/button' +import { MenuModule } from 'primeng/menu' +import { SkeletonModule } from 'primeng/skeleton' +import { DynamicPipe } from '../../pipes/dynamic.pipe' +import { StorybookTranslateModule } from '../../storybook-translate.module' +import { DiagramComponent } from './diagram.component' +import { DiagramType } from '../../../model/diagram-type' +import { DiagramData } from '../../../model/diagram-data' +import { ChartModule } from 'primeng/chart' +import { SelectButtonModule } from 'primeng/selectbutton' +import { FormsModule } from '@angular/forms' + +export default { + title: 'DiagramComponent', + component: DiagramComponent, + argTypes: { + diagramType: { + options: [DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR, DiagramType.PIE], + control: { type: 'select' }, + }, + }, + decorators: [ + applicationConfig({ + providers: [importProvidersFrom(BrowserModule), importProvidersFrom(BrowserAnimationsModule)], + }), + moduleMetadata({ + declarations: [DiagramComponent, DynamicPipe], + imports: [MenuModule, BreadcrumbModule, ButtonModule, SkeletonModule, StorybookTranslateModule, ChartModule, SelectButtonModule, FormsModule], + }), + ], +} as Meta + +const Template: StoryFn = (args: DiagramComponent) => ({ + props: args, +}) + +const mockData: DiagramData[] = [ + { + label: 'Apples', + value: 10, + }, + { + label: 'Bananas', + value: 7, + }, + { + label: 'Oranges', + value: 3, + }, +] + +export const PieChart = { + render: Template, + + args: { + diagramType: DiagramType.PIE, + data: mockData, + }, +} + +export const HorizontalBarChart = { + render: Template, + + args: { + diagramType: DiagramType.HORIZONTAL_BAR, + data: mockData, + }, +} + +export const VerticalBarChart = { + render: Template, + + args: { + diagramType: DiagramType.VERTICAL_BAR, + data: mockData, + }, +} + +export const WithDiagramTypeSelection = { + render: Template, + args: { + diagramType: DiagramType.PIE, + data: mockData, + supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR] + } +} diff --git a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.ts b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.ts index 0810eb9e..55577114 100644 --- a/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/diagram/diagram.component.ts @@ -2,18 +2,34 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angu import { TranslateService } from '@ngx-translate/core' import { ChartData, ChartOptions } from 'chart.js' import * as d3 from 'd3-scale-chromatic' -import { ColorUtils } from '../../utils/colorutils' +import { PrimeIcons } from 'primeng/api' import { DiagramData } from '../../../model/diagram-data' import { DiagramType } from '../../../model/diagram-type' +import { ColorUtils } from '../../utils/colorutils' + +export interface DiagramLayouts { + icon: string + layout: DiagramType + title?: string + titleKey: string +} + +const allDiagramTypes: DiagramLayouts[] = [ + { icon: PrimeIcons.CHART_PIE, layout: DiagramType.PIE, titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.PIE' }, + { icon: PrimeIcons.BARS, layout: DiagramType.HORIZONTAL_BAR, titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.HORIZONTAL_BAR' }, + { icon: PrimeIcons.CHART_BAR, layout: DiagramType.VERTICAL_BAR, titleKey: 'OCX_DIAGRAM.SWITCH_DIAGRAM_TYPE.VERTICAL_BAR' }, +] @Component({ selector: 'ocx-diagram', templateUrl: './diagram.component.html', + styleUrls: ['./diagram.component.scss'] }) export class DiagramComponent implements OnInit, OnChanges { @Input() data: DiagramData[] | undefined @Input() sumKey = 'OCX_DIAGRAM.SUM' private _diagramType: DiagramType = DiagramType.PIE + selectedDiagramType: DiagramLayouts | undefined public chartType = 'pie' @Input() get diagramType(): DiagramType { @@ -21,12 +37,24 @@ export class DiagramComponent implements OnInit, OnChanges { } set diagramType(value: DiagramType) { this._diagramType = value + this.selectedDiagramType = allDiagramTypes.find((v) => v.layout === value) this.chartType = this.diagramTypeToChartType(value) } + private _supportedDiagramTypes: DiagramType[] = [] + @Input() + get supportedDiagramTypes(): DiagramType[] { + return this._supportedDiagramTypes + } + set supportedDiagramTypes(value: DiagramType[]) { + this._supportedDiagramTypes = value + this.shownDiagramTypes = allDiagramTypes.filter((vl) => this.supportedDiagramTypes.includes(vl.layout)) + } @Output() dataSelected: EventEmitter = new EventEmitter() + @Output() diagramTypeChanged: EventEmitter = new EventEmitter() chartOptions: ChartOptions | undefined chartData: ChartData | undefined amountOfData: number | undefined | null + shownDiagramTypes: DiagramLayouts[] = [] // Changing the colorRangeInfo, will change the range of the color palette of the diagram. private colorRangeInfo = { colorStart: 0, @@ -90,6 +118,12 @@ export class DiagramComponent implements OnInit, OnChanges { dataClicked(event: []) { this.dataSelected.emit(event.length) } + + onDiagramTypeChanged(event: any) { + this.diagramType = event.value.layout + this.generateChart(this.colorScale, this.colorRangeInfo) + this.diagramTypeChanged.emit(event.value.layout) + } } function interpolateColors(amountOfData: number, colorScale: any, colorRangeInfo: any) { return ColorUtils.interpolateColors(amountOfData, colorScale, colorRangeInfo) diff --git a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.html b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.html index 55d6de89..56631dba 100644 --- a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.html +++ b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.html @@ -1 +1,8 @@ - + 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 4fc6300f..cd83e044 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 @@ -13,6 +13,7 @@ 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' +import { DiagramType } from '../../../model/diagram-type' describe('GroupByCountDiagramComponent', () => { let translateService: TranslateService @@ -195,4 +196,22 @@ describe('GroupByCountDiagramComponent', () => { const definedSumKeyTranslation = translateService.instant(definedSumKey) expect(displayedText).toEqual(definedSumKeyTranslation) }) + + it('should not display a selectButton on the diagram by default', async () => { + expect(component.supportedDiagramTypes).toEqual([]) + + const diagram = await loader.getHarness(DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + + expect(diagramTypeSelectButton).toBe(null) + }) + + it('should display a selectButton on the diagram if supportedDiagramTypes is specified', async () => { + component.supportedDiagramTypes = [DiagramType.PIE, DiagramType.HORIZONTAL_BAR] + + const diagram = await loader.getHarness(DiagramHarness) + const diagramTypeSelectButton = await diagram.getDiagramTypeSelectButton() + + expect(diagramTypeSelectButton).toBeTruthy() + }) }) diff --git a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.stories.ts b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.stories.ts new file mode 100644 index 00000000..f941de54 --- /dev/null +++ b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.stories.ts @@ -0,0 +1,124 @@ +import { importProvidersFrom } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { Meta, StoryFn, applicationConfig, moduleMetadata } from '@storybook/angular' +import { BreadcrumbModule } from 'primeng/breadcrumb' +import { ButtonModule } from 'primeng/button' +import { MenuModule } from 'primeng/menu' +import { SkeletonModule } from 'primeng/skeleton' +import { DynamicPipe } from '../../pipes/dynamic.pipe' +import { StorybookTranslateModule } from '../../storybook-translate.module' +import { DiagramType } from '../../../model/diagram-type' +import { ChartModule } from 'primeng/chart' +import { SelectButtonModule } from 'primeng/selectbutton' +import { GroupByCountDiagramComponent } from './group-by-count-diagram.component' +import { DiagramComponent } from '../diagram/diagram.component' +import { ColumnType } from '../../../model/column-type.model' + +export default { + title: 'GroupByCountDiagramComponent', + component: GroupByCountDiagramComponent, + argTypes: { + diagramType: { + options: [DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR, DiagramType.PIE], + control: { type: 'select' }, + }, + }, + decorators: [ + applicationConfig({ + providers: [importProvidersFrom(BrowserModule), importProvidersFrom(BrowserAnimationsModule)], + }), + moduleMetadata({ + declarations: [GroupByCountDiagramComponent, DiagramComponent, DynamicPipe], + imports: [MenuModule, BreadcrumbModule, ButtonModule, SkeletonModule, StorybookTranslateModule, ChartModule, SelectButtonModule], + }), + ], +} as Meta + +const Template: StoryFn = (args: GroupByCountDiagramComponent) => ({ + props: args, +}) + +const mockData = [ + { + id: 1, + fruitType: 'Apple', + name: 'Apple1' + }, + { + id: 2, + fruitType: 'Apple', + name: 'Apple2' + }, + { + id: 3, + fruitType: 'Apple', + name: 'Apple3' + }, + { + id: 4, + fruitType: 'Banana', + name: 'Banana1' + }, + { + id: 5, + fruitType: 'Banana', + name: 'Banana2' + } +] + +export const PieChart = { + render: Template, + + args: { + diagramType: DiagramType.PIE, + data: mockData, + column: { + id: 'fruitType', + type: ColumnType.STRING + }, + sumKey: 'Total' + }, +} + +export const HorizontalBarChart = { + render: Template, + + args: { + diagramType: DiagramType.HORIZONTAL_BAR, + data: mockData, + column: { + id: 'fruitType', + type: ColumnType.STRING + }, + sumKey: 'Total' + }, +} + +export const VerticalBarChart = { + render: Template, + + args: { + diagramType: DiagramType.VERTICAL_BAR, + data: mockData, + column: { + id: 'fruitType', + type: ColumnType.STRING + }, + sumKey: 'Total' + }, +} + +export const WithDiagramTypeSelection = { + render: Template, + args: { + diagramType: DiagramType.PIE, + data: mockData, + supportedDiagramTypes: [DiagramType.PIE, DiagramType.HORIZONTAL_BAR, DiagramType.VERTICAL_BAR], + column: { + id: 'fruitType', + type: ColumnType.STRING + }, + sumKey: 'Total' + } +} diff --git a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.ts b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.ts index 6d1c6fe4..48f4d702 100644 --- a/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.ts +++ b/libs/portal-integration-angular/src/lib/core/components/group-by-count-diagram/group-by-count-diagram.component.ts @@ -13,7 +13,18 @@ import { DiagramType } from '../../../model/diagram-type' }) export class GroupByCountDiagramComponent implements OnInit { @Input() sumKey = 'SEARCH.SUMMARY_TITLE' - @Input() type = DiagramType.PIE + @Input() diagramType = DiagramType.PIE + /** + * @deprecated Will be replaced by diagramType + */ + @Input() + get type(): DiagramType { + return this.diagramType + } + set type(value: DiagramType) { + this.diagramType = value + } + @Input() supportedDiagramTypes: DiagramType[] = [] private _data$ = new BehaviorSubject([]) @Input() get data(): unknown[] { @@ -52,6 +63,7 @@ export class GroupByCountDiagramComponent implements OnInit { } @Output() dataSelected: EventEmitter = new EventEmitter() + @Output() diagramTypeChanged: EventEmitter = new EventEmitter() constructor(private translateService: TranslateService) {} @@ -83,4 +95,9 @@ export class GroupByCountDiagramComponent implements OnInit { dataClicked(event: any) { this.dataSelected.emit(event) } + + onDiagramTypeChanged(newDiagramType: DiagramType) { + this.diagramType = newDiagramType + this.diagramTypeChanged.emit(newDiagramType) + } } diff --git a/libs/portal-integration-angular/testing/diagram.harness.ts b/libs/portal-integration-angular/testing/diagram.harness.ts index 1eb0f43b..8dfe3d22 100644 --- a/libs/portal-integration-angular/testing/diagram.harness.ts +++ b/libs/portal-integration-angular/testing/diagram.harness.ts @@ -1,5 +1,6 @@ import { ComponentHarness } from '@angular/cdk/testing' import { PChartHarness } from './primeng/p-chart.harness' +import { PSelectButtonHarness } from './primeng/p-selectButton.harness' export class DiagramHarness extends ComponentHarness { static hostSelector = 'ocx-diagram' @@ -13,4 +14,12 @@ export class DiagramHarness extends ComponentHarness { async getSumLabel(): Promise { return (await this.locatorForOptional('.sumKey span[name="sumLabel"]')())?.text() } + + async getDiagramTypeSelectButton() { + return (await this.locatorForOptional('p-selectButton[name="diagram-type-select-button"]')()) + } + + async getAllSelectionButtons() { + return await (await this.locatorFor(PSelectButtonHarness)()).getAllButtons() + } } From b8cff9272a536ed14efe56ab7f13e475e2e656af Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 26 Feb 2024 10:53:17 +0000 Subject: [PATCH 3/3] chore(release): -v4.8.0 [skip ci] ## [4.8.0](https://github.com/onecx/onecx-portal-ui-libs/compare/v4.7.0...v4.8.0) (2024-02-26) ### Features * allow dynamically switching diagram type ([#144](https://github.com/onecx/onecx-portal-ui-libs/issues/144)) ([ac0e2e0](https://github.com/onecx/onecx-portal-ui-libs/commit/ac0e2e051f7dee12e45561bfda1cebf642d0504c)) * save and edit search config component ([#139](https://github.com/onecx/onecx-portal-ui-libs/issues/139)) ([635339a](https://github.com/onecx/onecx-portal-ui-libs/commit/635339aae8dedfa96d6a77798de591ef4ae49829)) ### Bug Fixes * check length of occurences and ([#141](https://github.com/onecx/onecx-portal-ui-libs/issues/141)) ([782cb27](https://github.com/onecx/onecx-portal-ui-libs/commit/782cb27643a32c7fcf5f9e14a3a4876cae4972e5)) * current page should persist between layout changes ([#142](https://github.com/onecx/onecx-portal-ui-libs/issues/142)) ([e023add](https://github.com/onecx/onecx-portal-ui-libs/commit/e023adda69ae2a90cb4a5b82ba87fdb60eba0670)) * empty message allignment to amount of columns ([#140](https://github.com/onecx/onecx-portal-ui-libs/issues/140)) ([3459c67](https://github.com/onecx/onecx-portal-ui-libs/commit/3459c676903225d1356ee7116951f3e25c60feb1)) * removed conflicting style ([#137](https://github.com/onecx/onecx-portal-ui-libs/issues/137)) ([7ddcd17](https://github.com/onecx/onecx-portal-ui-libs/commit/7ddcd17c0e025708353d8a525bb07ac13500cb6d)) --- CHANGELOG.md | 16 ++++++++++++++++ libs/accelerator/package.json | 2 +- libs/integration-interface/package.json | 2 +- libs/keycloak-auth/package.json | 2 +- libs/portal-integration-angular/package.json | 2 +- libs/portal-layout-styles/package.json | 2 +- package.json | 2 +- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd38bfd..0adb7f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [4.8.0](https://github.com/onecx/onecx-portal-ui-libs/compare/v4.7.0...v4.8.0) (2024-02-26) + + +### Features + +* allow dynamically switching diagram type ([#144](https://github.com/onecx/onecx-portal-ui-libs/issues/144)) ([ac0e2e0](https://github.com/onecx/onecx-portal-ui-libs/commit/ac0e2e051f7dee12e45561bfda1cebf642d0504c)) +* save and edit search config component ([#139](https://github.com/onecx/onecx-portal-ui-libs/issues/139)) ([635339a](https://github.com/onecx/onecx-portal-ui-libs/commit/635339aae8dedfa96d6a77798de591ef4ae49829)) + + +### Bug Fixes + +* check length of occurences and ([#141](https://github.com/onecx/onecx-portal-ui-libs/issues/141)) ([782cb27](https://github.com/onecx/onecx-portal-ui-libs/commit/782cb27643a32c7fcf5f9e14a3a4876cae4972e5)) +* current page should persist between layout changes ([#142](https://github.com/onecx/onecx-portal-ui-libs/issues/142)) ([e023add](https://github.com/onecx/onecx-portal-ui-libs/commit/e023adda69ae2a90cb4a5b82ba87fdb60eba0670)) +* empty message allignment to amount of columns ([#140](https://github.com/onecx/onecx-portal-ui-libs/issues/140)) ([3459c67](https://github.com/onecx/onecx-portal-ui-libs/commit/3459c676903225d1356ee7116951f3e25c60feb1)) +* removed conflicting style ([#137](https://github.com/onecx/onecx-portal-ui-libs/issues/137)) ([7ddcd17](https://github.com/onecx/onecx-portal-ui-libs/commit/7ddcd17c0e025708353d8a525bb07ac13500cb6d)) + ## [4.7.0](https://github.com/onecx/onecx-portal-ui-libs/compare/v4.6.0...v4.7.0) (2024-02-21) diff --git a/libs/accelerator/package.json b/libs/accelerator/package.json index 75802a76..caf7e084 100644 --- a/libs/accelerator/package.json +++ b/libs/accelerator/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/accelerator", - "version": "4.7.0", + "version": "4.8.0", "peerDependencies": { "tslib": "^2.3.0", "rxjs": "7.8.1" diff --git a/libs/integration-interface/package.json b/libs/integration-interface/package.json index ba9b7752..e6718c07 100644 --- a/libs/integration-interface/package.json +++ b/libs/integration-interface/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/integration-interface", - "version": "4.7.0", + "version": "4.8.0", "peerDependencies": { "tslib": "^2.3.0", "rxjs": "7.8.1", diff --git a/libs/keycloak-auth/package.json b/libs/keycloak-auth/package.json index ee1ebfe7..c6fda510 100644 --- a/libs/keycloak-auth/package.json +++ b/libs/keycloak-auth/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/keycloak-auth", - "version": "4.7.0", + "version": "4.8.0", "peerDependencies": { "@angular/common": ">=15.2.7", "@angular/core": ">=15.2.7", diff --git a/libs/portal-integration-angular/package.json b/libs/portal-integration-angular/package.json index a3e1738d..634c9706 100644 --- a/libs/portal-integration-angular/package.json +++ b/libs/portal-integration-angular/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/portal-integration-angular", - "version": "4.7.0", + "version": "4.8.0", "peerDependencies": { "@angular-architects/module-federation": "15.0.0", "@angular/common": "^15.2.7", diff --git a/libs/portal-layout-styles/package.json b/libs/portal-layout-styles/package.json index 66892e96..46676cce 100644 --- a/libs/portal-layout-styles/package.json +++ b/libs/portal-layout-styles/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/portal-layout-styles", - "version": "4.7.0", + "version": "4.8.0", "peerDependencies": { "tslib": "^2.5.0" }, diff --git a/package.json b/package.json index 3fdab2f0..72fa6f79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onecx/onecx-portal-ui-libs", - "version": "4.7.0", + "version": "4.8.0", "license": "Apache-2.0", "scripts": { "sass": "npx sass libs/portal-integration-angular/assets/styles.scss libs/portal-integration-angular/assets/output.css",