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",