From 2e22030def7f02d692415f19d6ad8898997ed300 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 20 Aug 2024 12:00:00 +0200 Subject: [PATCH] Remove Select from AQL builder in Criteria mode --- .../aql-builder-contains.component.html | 31 +++++++- .../aql-builder-contains.component.spec.ts | 2 + .../aql-builder-contains.component.ts | 12 +++ .../aql-builder-select-item.component.html | 2 +- .../aql-builder-select-item.component.spec.ts | 2 +- .../aql-builder-select.component.html | 73 +++++++++++-------- .../aql-builder-select.component.ts | 6 ++ .../aql-builder-template-tree.component.html | 3 +- .../aql-editor-creator.component.spec.ts | 4 +- .../aql-editor-creator.component.ts | 11 +++ .../dialog-aql-builder.component.html | 12 ++- .../dialog-aql-builder.component.spec.ts | 23 ++++-- .../dialog-aql-builder.component.ts | 30 +++++++- .../tests/select-click-testcases.ts | 46 +++++++++++- .../models/aqb/aqb-select-destination.enum.ts | 1 + .../models/aqb/aqb-select-item-ui.model.ts | 11 ++- src/app/shared/models/aqb/aqb-ui.model.ts | 4 +- src/assets/i18n/de.json | 1 + src/assets/i18n/en.json | 1 + 19 files changed, 217 insertions(+), 58 deletions(-) diff --git a/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.html b/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.html index 69d32fc1f..f34770dfa 100644 --- a/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.html +++ b/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.html @@ -1,6 +1,33 @@ -

From

+

From

-
+
+

From

+
+ +
+
+ +
{ let component: AqlBuilderContainsComponent @@ -35,6 +36,7 @@ describe('AqlBuilderContainsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AqlBuilderContainsComponent, ContainsGroupStubComponent], + imports: [FontAwesomeTestingModule], }).compileComponents() }) diff --git a/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.ts b/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.ts index 96980bf0c..c57cc0890 100644 --- a/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.ts +++ b/src/app/modules/aqls/components/aql-builder-contains/aql-builder-contains.component.ts @@ -1,6 +1,8 @@ import { Component, Input } from '@angular/core' import { AqbContainsCompositionUiModel } from '../../../../shared/models/aqb/aqb-contains-composition-ui.model' import { AqbUiModel } from '../../../../shared/models/aqb/aqb-ui.model' +import { AqbSelectDestination } from '../../../../shared/models/aqb/aqb-select-destination.enum' +import { AqlBuilderDialogMode } from '../../../../shared/models/archetype-query-builder/aql-builder-dialog-mode.enum' @Component({ selector: 'num-aql-builder-contains', @@ -16,6 +18,9 @@ export class AqlBuilderContainsComponent { @Input() compositions: AqbContainsCompositionUiModel[] = [] + @Input() + dialogMode: AqlBuilderDialogMode = AqlBuilderDialogMode.Criteria + deleteCompositionByReferenceId(compositionReferenceId: number): void { this.aqbModel.handleDeletionByCompositionReferenceIds([compositionReferenceId]) this.compositions = this.compositions.filter( @@ -26,4 +31,11 @@ export class AqlBuilderContainsComponent { deleteArchetypesByReferenceIds(archetypeReferenceIds: number[]): void { this.aqbModel.handleDeletionByArchetypeReferenceIds(archetypeReferenceIds) } + + setDestination(): void { + this.aqbModel.selectDestination = AqbSelectDestination.From + } + + protected readonly AqbSelectDestination = AqbSelectDestination + protected readonly AqlBuilderDialogMode = AqlBuilderDialogMode } diff --git a/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.html b/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.html index aa2dddb4a..3f4a291e9 100644 --- a/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.html +++ b/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.html @@ -1,7 +1,7 @@
{{ item.name | archetype: true }} - ({{ item.templateId }}) + ({{ item.templateId }})
diff --git a/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.spec.ts b/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.spec.ts index aadf470be..d6417fa7f 100644 --- a/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.spec.ts +++ b/src/app/modules/aqls/components/aql-builder-select-item/aql-builder-select-item.component.spec.ts @@ -30,7 +30,7 @@ describe('AqlBuilderSelectItemComponent', () => { containmentTreeNode, compositionReferenceId, archetypeReferenceId, - false, + 'archetype', templateId ) diff --git a/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.html b/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.html index be5af63de..47b554e8f 100644 --- a/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.html +++ b/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.html @@ -1,36 +1,45 @@ -

Select

-
- +
+

Select

+

+ {{ 'QUERIES.NO_SELECT' | translate }} +

-
- +

Select

+
+ role="button" + tabindex="0" + aria-labelledby="section-heading" + (keydown.enter)="setDestination()" + (keydown.space)="setDestination()" + (click)="setDestination()" + data-test="aqb__select__set-destination-button" + > + +
+
+ +
diff --git a/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.ts b/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.ts index e9396441f..a777d56ec 100644 --- a/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.ts +++ b/src/app/modules/aqls/components/aql-builder-select/aql-builder-select.component.ts @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core' import { AqbSelectDestination } from '../../../../shared/models/aqb/aqb-select-destination.enum' import { AqbUiModel } from '../../../../shared/models/aqb/aqb-ui.model' +import { AqlBuilderDialogMode } from '../../../../shared/models/archetype-query-builder/aql-builder-dialog-mode.enum' @Component({ selector: 'num-aql-builder-select', @@ -15,6 +16,9 @@ export class AqlBuilderSelectComponent { @Input() aqbModel: AqbUiModel + @Input() + dialogMode: AqlBuilderDialogMode = AqlBuilderDialogMode.Criteria + deleteItem(index: number): void { this.aqbModel.select.splice(index, 1) } @@ -22,4 +26,6 @@ export class AqlBuilderSelectComponent { setDestination(): void { this.aqbModel.selectDestination = AqbSelectDestination.Select } + + protected readonly AqlBuilderDialogMode = AqlBuilderDialogMode } diff --git a/src/app/modules/aqls/components/aql-builder-template-tree/aql-builder-template-tree.component.html b/src/app/modules/aqls/components/aql-builder-template-tree/aql-builder-template-tree.component.html index 75a84036e..c0dae7d9a 100644 --- a/src/app/modules/aqls/components/aql-builder-template-tree/aql-builder-template-tree.component.html +++ b/src/app/modules/aqls/components/aql-builder-template-tree/aql-builder-template-tree.component.html @@ -42,7 +42,8 @@ class="mat-tree-node__display-name" [ngClass]="{ 'mat-tree-node__display-name--disabled': - mode === Mode.DataRetrieval && selectDestination === Destination.Select + (mode === Mode.DataRetrieval && selectDestination === Destination.Select) || + (mode === Mode.Criteria && selectDestination === Destination.From) }" >{{ node.displayName }} diff --git a/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.spec.ts b/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.spec.ts index 699ecce75..84c580975 100644 --- a/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.spec.ts +++ b/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.spec.ts @@ -149,7 +149,7 @@ describe('AqlEditorCeatorComponent', () => { describe('When a query is supposed to be created with the builder', () => { const dialogContentPayload: IAqlBuilderDialogInput = { - mode: AqlBuilderDialogMode.Criteria, + mode: AqlBuilderDialogMode.Search, model: new AqbUiModel(), } const dialogConfig: DialogConfig = { @@ -158,7 +158,7 @@ describe('AqlEditorCeatorComponent', () => { } it('should open the dialog with the config including the content payload', () => { - component.openBuilderDialog(AqlBuilderDialogMode.Criteria) + component.openBuilderDialog(AqlBuilderDialogMode.Search) expect(mockDialogService.openDialog).toHaveBeenCalledTimes(1) expect(dialogCallParameter.dialogContentComponent).toEqual( dialogConfig.dialogContentComponent diff --git a/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.ts b/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.ts index a9e872f28..f8fe03fe2 100644 --- a/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.ts +++ b/src/app/modules/aqls/components/aql-editor-creator/aql-editor-creator.component.ts @@ -18,6 +18,9 @@ import { VALIDATION_ERROR_CONFIG, VALIDATION_SUCCESS_CONFIG, } from './constants' +import { AqbSelectDestination } from '../../../../shared/models/aqb/aqb-select-destination.enum' +import { AqbSelectItemUiModel } from '../../../../shared/models/aqb/aqb-select-item-ui.model' +import { IContainmentTreeNode } from '../../models/containment-tree-node.interface' @Component({ selector: 'num-aql-editor-creator', @@ -115,6 +118,14 @@ export class AqlEditorCeatorComponent { } openBuilderDialog(mode: AqlBuilderDialogMode): void { + if (mode === AqlBuilderDialogMode.Criteria) { + const node = new (class implements IContainmentTreeNode { + displayName = 'EHR' + })() + this.aqbModel.selectDestination = AqbSelectDestination.From + this.aqbModel.select = [new AqbSelectItemUiModel(node, 0, 0, 'ehr', '')] + } + const dialogContentPayload: IAqlBuilderDialogInput = { mode: mode, model: this.aqbModel, diff --git a/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.html b/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.html index 9712ac96f..00bed94db 100644 --- a/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.html +++ b/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.html @@ -9,10 +9,14 @@ >
- + {{ 'BUTTON.APPLY_SELECTION' | translate }} { let component: DialogAqlBuilderComponent @@ -36,11 +36,13 @@ describe('DialogAqlBuilderComponent', () => { @Component({ selector: 'num-aql-builder-select', template: '' }) class SelectStubComponent { @Input() aqbModel: any + @Input() dialogMode: any } @Component({ selector: 'num-aql-builder-contains', template: '' }) class ContainsStubComponent { @Input() compositions: any @Input() aqbModel: any + @Input() dialogMode: any } @Component({ selector: 'num-aql-builder-where', template: '' }) class WhereStubComponent { @@ -86,10 +88,10 @@ describe('DialogAqlBuilderComponent', () => { jest.clearAllMocks() }) - describe('When the dialog is in aqlEditor mode', () => { + describe('When the dialog is in Search mode', () => { beforeEach(() => { component.dialogInput = { - mode: AqlBuilderDialogMode.Criteria, + mode: AqlBuilderDialogMode.Search, model: new AqbUiModel(), } @@ -100,9 +102,20 @@ describe('DialogAqlBuilderComponent', () => { expect(component).toBeTruthy() expect(aqlEditorService.getTemplates).toHaveBeenCalled() }) + }) + + describe('When the dialog is', () => { + beforeEach(() => { + component.dialogInput = { + mode: AqlBuilderDialogMode.Search, //overwritten in test + model: new AqbUiModel(), + } + + fixture.detectChanges() + }) - test.each(selectClickTestCases)( - 'should call or not call the aqb Model to handle the clickEvent', + test.each(selectClickTestCases)( + 'in $mode mode should call or not call the aqb Model to handle the clickEvent (%#)', (testcase) => { jest.spyOn(component.aqbModel, 'handleElementSelect') diff --git a/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.ts b/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.ts index 3825611c4..513804a31 100644 --- a/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.ts +++ b/src/app/modules/aqls/components/dialog-aql-builder/dialog-aql-builder.component.ts @@ -83,7 +83,22 @@ export class DialogAqlBuilderComponent return false } - isAllowedSelectionInEditor(clickEvent: IAqbSelectClick): boolean { + + isAllowedSelectionInCriteria(clickEvent: IAqbSelectClick): boolean { + if (this.aqbModel.selectDestination === AqbSelectDestination.From) { + if (!clickEvent.item.rmType) { + return true + } + } else { + if (clickEvent.item.rmType) { + return true + } + } + + return false + } + + isAllowedSelectionInSearch(clickEvent: IAqbSelectClick): boolean { if (this.aqbModel.selectDestination === AqbSelectDestination.Select) { return true } else { @@ -96,9 +111,14 @@ export class DialogAqlBuilderComponent } isAllowedSelection(clickEvent: IAqbSelectClick): boolean { - return this.dialogInput.mode === AqlBuilderDialogMode.DataRetrieval - ? this.isAllowedSelectionInRetrieval(clickEvent) - : this.isAllowedSelectionInEditor(clickEvent) + switch (this.dialogInput.mode) { + case AqlBuilderDialogMode.DataRetrieval: + return this.isAllowedSelectionInRetrieval(clickEvent) + case AqlBuilderDialogMode.Criteria: + return this.isAllowedSelectionInCriteria(clickEvent) + case AqlBuilderDialogMode.Search: + return this.isAllowedSelectionInSearch(clickEvent) + } } handleItemSelect(clickEvent: IAqbSelectClick): void { @@ -126,4 +146,6 @@ export class DialogAqlBuilderComponent handleDialogCancel(): void { this.closeDialog.emit() } + + protected readonly AqlBuilderDialogMode = AqlBuilderDialogMode } diff --git a/src/app/modules/aqls/components/dialog-aql-builder/tests/select-click-testcases.ts b/src/app/modules/aqls/components/dialog-aql-builder/tests/select-click-testcases.ts index ccac54034..a7feb00bd 100644 --- a/src/app/modules/aqls/components/dialog-aql-builder/tests/select-click-testcases.ts +++ b/src/app/modules/aqls/components/dialog-aql-builder/tests/select-click-testcases.ts @@ -31,9 +31,10 @@ const selectedItemWithoutRmType: IContainmentTreeNode = { } export const selectClickTestCases: ISelectClickTest[] = [ + // Select { result: true, - mode: AqlBuilderDialogMode.Criteria, + mode: AqlBuilderDialogMode.Search, selectDestination: AqbSelectDestination.Select, clickEvent: { item: selectedItemWithRmType, @@ -43,7 +44,7 @@ export const selectClickTestCases: ISelectClickTest[] = [ }, { result: true, - mode: AqlBuilderDialogMode.Criteria, + mode: AqlBuilderDialogMode.Search, selectDestination: AqbSelectDestination.Select, clickEvent: { item: selectedItemWithoutRmType, @@ -82,6 +83,16 @@ export const selectClickTestCases: ISelectClickTest[] = [ templateId: 'temp1', }, }, + { + result: false, + mode: AqlBuilderDialogMode.Search, + selectDestination: AqbSelectDestination.Where, + clickEvent: { + item: selectedItemWithoutRmType, + compositionId: 'comp1', + templateId: 'temp1', + }, + }, { result: false, mode: AqlBuilderDialogMode.DataRetrieval, @@ -102,6 +113,16 @@ export const selectClickTestCases: ISelectClickTest[] = [ templateId: 'temp1', }, }, + { + result: true, + mode: AqlBuilderDialogMode.Search, + selectDestination: AqbSelectDestination.Where, + clickEvent: { + item: selectedItemWithRmType, + compositionId: 'comp1', + templateId: 'temp1', + }, + }, { result: true, mode: AqlBuilderDialogMode.DataRetrieval, @@ -112,4 +133,25 @@ export const selectClickTestCases: ISelectClickTest[] = [ templateId: 'temp1', }, }, + // From + { + result: true, + mode: AqlBuilderDialogMode.Criteria, + selectDestination: AqbSelectDestination.From, + clickEvent: { + item: selectedItemWithoutRmType, + compositionId: 'comp1', + templateId: 'temp1', + }, + }, + { + result: false, + mode: AqlBuilderDialogMode.Criteria, + selectDestination: AqbSelectDestination.From, + clickEvent: { + item: selectedItemWithRmType, + compositionId: 'comp1', + templateId: 'temp1', + }, + }, ] diff --git a/src/app/shared/models/aqb/aqb-select-destination.enum.ts b/src/app/shared/models/aqb/aqb-select-destination.enum.ts index 8995ef4b0..8dcf21b58 100644 --- a/src/app/shared/models/aqb/aqb-select-destination.enum.ts +++ b/src/app/shared/models/aqb/aqb-select-destination.enum.ts @@ -1,4 +1,5 @@ export enum AqbSelectDestination { Select = 'SELECT', + From = 'FROM', Where = 'WHERE', } diff --git a/src/app/shared/models/aqb/aqb-select-item-ui.model.ts b/src/app/shared/models/aqb/aqb-select-item-ui.model.ts index f0ee31124..af3a0dcc8 100644 --- a/src/app/shared/models/aqb/aqb-select-item-ui.model.ts +++ b/src/app/shared/models/aqb/aqb-select-item-ui.model.ts @@ -13,14 +13,14 @@ export class AqbSelectItemUiModel { humanReadablePath: string compositionReferenceId: number archetypeReferenceId: number - isComposition: boolean + modelType: 'composition' | 'archetype' | 'ehr' templateId: string constructor( item: IContainmentTreeNode, compositionReferenceId: number, archetypeReferenceId: number, - isComposition: boolean, + modelType: 'composition' | 'archetype' | 'ehr', templateId: string ) { this.name = item.name || item.archetypeId @@ -30,7 +30,7 @@ export class AqbSelectItemUiModel { this.humanReadablePath = item.humanReadablePath this.compositionReferenceId = compositionReferenceId this.archetypeReferenceId = archetypeReferenceId - this.isComposition = isComposition + this.modelType = modelType this.templateId = templateId } @@ -39,7 +39,10 @@ export class AqbSelectItemUiModel { _type: AqbNodeType.SelectExpression, columnExpression: { _type: AqbNodeType.IdentifiedPath, - root: `${this.isComposition ? 'c' : 'o'}${this.archetypeReferenceId}`, + root: + this.modelType === 'ehr' + ? 'e' + : `${this.modelType === 'composition' ? 'c' : 'o'}${this.archetypeReferenceId}`, ...(this.aqlPath && { path: this.aqlPath }), }, ...(this.givenName && { alias: this.givenName }), diff --git a/src/app/shared/models/aqb/aqb-ui.model.ts b/src/app/shared/models/aqb/aqb-ui.model.ts index 2c6222c17..219686a46 100644 --- a/src/app/shared/models/aqb/aqb-ui.model.ts +++ b/src/app/shared/models/aqb/aqb-ui.model.ts @@ -54,7 +54,7 @@ export class AqbUiModel { isComposition, clickEvent.templateId ) - } else { + } else if (this.selectDestination === AqbSelectDestination.Where) { const userGeneratedWhereClause = this.where.children[1] as AqbWhereGroupUiModel this.pushToWhereClause( userGeneratedWhereClause, @@ -110,7 +110,7 @@ export class AqbUiModel { clickEvent.item, compositionReferenceId, archetypeReferenceId, - isComposition, + isComposition ? 'composition' : 'archetype', templateId ) this.select.push(aqbSelect) diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index db9cf13ec..4e0b05d56 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -101,6 +101,7 @@ "COMPILE_ERROR_MESSAGE": "Das Kriterium konnte nicht kompiliert werden. Bitte überprüfen Sie die Konfiguration des Kriteriums und versuchen Sie es erneut.", "GET_COMPOSITION_ERROR": "Dar Rückgabeparameter kann nicht geladen werden. Bitte schließen Sie das Fenster und versuchen Sie es erneut.", "COLUMNS": "Spalten", + "NO_SELECT": "Bitte beachten Sie: Für die Erstellung eines Kriteriums wird kein SELECT verwendet.", "WHERE_RESTRICTED_TO_COHORT": "Bitte beachten Sie: Die WHERE-Klausel ist auf die Kohorte beschränkt.", "VALIDATION_ERROR": "Die Syntax des Kriteriums ist nicht valide", "VALIDATION_SUCCESS": "Die Syntax des Kriteriums ist valide", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 6d917729a..b9fd1d1c4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -101,6 +101,7 @@ "COMPILE_ERROR_MESSAGE": "The criterium could not be complied. Please check the criterium configuration and try again.", "GET_COMPOSITION_ERROR": "The template cannot be loaded. Please close the window and try again.", "COLUMNS": "Columns", + "NO_SELECT": "Please note: SELECT is not used to create a criterion.", "WHERE_RESTRICTED_TO_COHORT": "Please note: the WHERE clause is restricted to the cohort.", "VALIDATION_ERROR": "The syntax of the criterium is not valid", "VALIDATION_SUCCESS": "The syntax of the criterium is valid",