Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New page "Search with AQL" #624

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ export const routes: Routes = [
(m) => m.SearchModule
),
},
{
path: 'search-with-aql',
canLoad: [RoleGuard, AuthGuard],
data: {
navId: 'search-with-aql',
roles: [AvailableRoles.Manager],
onlyApprovedUsers: true,
},
loadChildren: () =>
import(
/* webpackChunkName: "SearchWithAql.Module" */ './modules/search-with-aql/search-with-aql.module'
).then((m) => m.SearchWithAqlModule),
},
{
path: 'projects',
canLoad: [RoleGuard, AuthGuard],
Expand Down
7 changes: 7 additions & 0 deletions src/app/core/constants/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { AvailableRoles } from 'src/app/shared/models/available-roles.enum'
import INavItem from '../../layout/models/nav-item.interface'
import { USERMANUAL } from './constants'
import { AvailableFeatures } from '../../shared/models/feature/available-features.enum'

export const mainNavItems: INavItem[] = [
{
routeTo: 'home',
icon: 'num-welcome',
translationKey: 'NAVIGATION.DASHBOARD',
},
{
routeTo: 'search-with-aql',
icon: 'search',
translationKey: 'NAVIGATION.SEARCH_WITH_AQL',
feature: [AvailableFeatures.SearchWithAql],
},
{
routeTo: 'search',
icon: 'search',
Expand Down
46 changes: 46 additions & 0 deletions src/app/core/services/query/query.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { BehaviorSubject, Observable, throwError } from 'rxjs'
import { catchError, tap } from 'rxjs/operators'
import { AppConfigService } from 'src/app/config/app-config.service'
import { IAqlExecutionResponse } from 'src/app/shared/models/aql/execution/aql-execution-response.interface'

@Injectable({
providedIn: 'root',
})
export class QueryService {
private baseUrl: string

private projectData: IAqlExecutionResponse = null
private projectDataSubject$ = new BehaviorSubject(this.projectData)
private query: string

constructor(
private appConfigService: AppConfigService,
private httpClient: HttpClient
) {
this.baseUrl = `${this.appConfigService.config.api.baseUrl}`
}

setQuery(query: string) {
this.query = query
}

getData(): Observable<IAqlExecutionResponse> {
return this.httpClient
.post<IAqlExecutionResponse>(`${this.baseUrl}/query/execute`, {
aql: this.query,
})
.pipe(
tap((res) => {
this.projectData = res
this.projectDataSubject$.next(res)
}),
catchError(this.handleError)
)
}

handleError(error: HttpErrorResponse): Observable<never> {
return throwError(() => error)
}
}
1 change: 1 addition & 0 deletions src/app/modules/aqls/aqls.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ import { AqlBuilderWhereGroupComponent } from './components/aql-builder-where-gr
AqlBuilderWhereGroupComponent,
],
imports: [CommonModule, AqlsRoutingModule, SharedModule, LayoutModule, CodeEditorModule],
exports: [AqlEditorCeatorComponent],
})
export class AqlsModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class AqlEditorCeatorComponent {
private dialogService: DialogService,
private aqlEditorService: AqlEditorService,
private aqlService: AqlService,
private toastMessageService: ToastMessageService
public toastMessageService: ToastMessageService
) {}
formatter = new NumAqlFormattingProvider()
formatSubject$ = new Subject<monaco.editor.IMarkerData[] | void>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<div fxLayout="column">
<section role="region" aria-labelledby="creator-label" fxLayoutGap="20px" class="num-margin-b-60">
<h2>{{ 'BUILD_QUERY' | translate }}</h2>
<mat-card>
<mat-card-content fxLayout="column" fxFlex="100%">
<p id="creator-label">
{{ 'SEARCH_WITH_AQL.BUILD_QUERY_CONTENT' | translate }}
</p>

<num-button
type="primary"
icon="plus"
class="num-margin-b-40"
(singleClick)="openBuilderDialog()"
data-test="aql-editor__aql-creator__open-builder-button"
>{{ 'BUTTON.OPEN_QUERY_BUILDER' | translate }}</num-button
>

<div style="height: 300px" class="num-margin-b-40">
<num-code-editor
class="editor"
style="height: 100%"
[(value)]="aqlQuery"
[formatObservable$]="formatObservable$"
[validationObservable$]="validationObservable$"
(editorInit)="onEditorInit($event)"
></num-code-editor>
</div>

<div class="button-row num-margin-b-20" fxLayout="row" fxLayoutGap="20px">
<num-button
type="primary"
icon="align-left"
(singleClick)="format()"
data-test="aql-editor__aql-creator__format-button"
>{{ 'BUTTON.FORMAT_QUERY' | translate }}</num-button
>

<num-button
type="primary"
icon="align-left"
(singleClick)="validate(true)"
data-test="aql-editor__aql-creator__validate-button"
>{{ 'BUTTON.VALIDATE_QUERY' | translate }}</num-button
>
</div>

<num-editor-determine-hits
[isButtonDisabled]="!isValidForExecution || isExecutionLoading"
[content]="determineHitsContent"
(clicked)="getData()"
></num-editor-determine-hits>
</mat-card-content>
</mat-card>
</section>

<section role="region" fxLayout="column" fxFlex="100%">
<h2 *ngIf="resultSet && !isDataSetLoading">{{ 'SEARCH_WITH_AQL.QUERY_DATA' | translate }}</h2>

<ng-container *ngIf="!isDataSetLoading; else loading">
<num-result-table
*ngIf="resultSet"
[resultSet]="resultSet"
[isDataSetLoading]="isDataSetLoading"
[index]="1"
[totalTables]="1"
class="num-margin-b-20"
data-test="manager-data-explorer__table"
></num-result-table>
</ng-container>

<ng-template #loading>
<mat-card class="mat-elevation-z1">
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="8px">
<mat-spinner color="accent" [diameter]="24"></mat-spinner>
<span>{{ 'DATA_EXPLORER.LOADING_RESULT_SET' | translate }}</span>
</div>
</mat-card>
</ng-template>
</section>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:host {
width: 100%;
}

.editor {
border-width: 1px;
border-style: solid;
border-color: grey;
border-radius: 6px;
padding: 20px 20px 20px 3px;
position: relative;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Component, OnDestroy } from '@angular/core'
import { AqlEditorCeatorComponent } from '../../../aqls/components/aql-editor-creator/aql-editor-creator.component'
import { DialogService } from '../../../../core/services/dialog/dialog.service'
import { AqlEditorService } from '../../../../core/services/aql-editor/aql-editor.service'
import { AqlService } from '../../../../core/services/aql/aql.service'
import { ToastMessageService } from '../../../../core/services/toast-message/toast-message.service'
import { QueryService } from '../../../../core/services/query/query.service'
import { RESULT_SET_LOADING_ERROR } from '../../../search/components/manager-data-explorer/constants'
import { IAqlExecutionResponse } from '../../../../shared/models/aql/execution/aql-execution-response.interface'
import { Subscription } from 'rxjs'

@Component({
templateUrl: './search.component.html',
styleUrls: ['../../../aqls/components/aql-editor-creator/aql-editor-creator.component.scss'],
})
export class SearchComponent extends AqlEditorCeatorComponent implements OnDestroy {
constructor(
dialogService: DialogService,
aqlEditorService: AqlEditorService,
aqlService: AqlService,
toastMessageService: ToastMessageService,
private queryService: QueryService
) {
super(dialogService, aqlEditorService, aqlService, toastMessageService)
}
private subscriptions = new Subscription()

resultSet: IAqlExecutionResponse
isDataSetLoading = false

ngOnDestroy(): void {
this.subscriptions.unsubscribe()
}

getData(): void {
this.determineHits()
this.isDataSetLoading = true
this.queryService.setQuery(this.aqlQuery)
this.subscriptions.add(
this.queryService.getData().subscribe({
next: (result) => {
this.resultSet = result
this.isDataSetLoading = false
},
error: () => {
this.resultSet = null
this.isDataSetLoading = false
this.toastMessageService.openToast(RESULT_SET_LOADING_ERROR)
},
})
)
}
}
16 changes: 16 additions & 0 deletions src/app/modules/search-with-aql/search-with-aql-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { SearchComponent } from './components/search-aql/search.component'

const routes: Routes = [
{
path: '',
component: SearchComponent,
},
]

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SearchWithAqlRoutingModule {}
25 changes: 25 additions & 0 deletions src/app/modules/search-with-aql/search-with-aql.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { SharedModule } from 'src/app/shared/shared.module'
import { LayoutModule } from 'src/app/layout/layout.module'
import { CohortBuilderModule } from '../cohort-builder/cohort-builder.module'
import { SharedProjectsModule } from '../projects/shared-projects.module'
import { CodeEditorModule } from '../code-editor/code-editor.module'
import { SearchComponent } from './components/search-aql/search.component'
import { SearchWithAqlRoutingModule } from './search-with-aql-routing.module'
import { AqlsModule } from '../aqls/aqls.module'

@NgModule({
declarations: [SearchComponent],
imports: [
CohortBuilderModule,
CommonModule,
LayoutModule,
SharedModule,
SharedProjectsModule,
CodeEditorModule,
SearchWithAqlRoutingModule,
AqlsModule,
],
})
export class SearchWithAqlModule {}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<section role="region" fxLayout="column">
<h4>
{{ 'TABLE' | translate }} {{ index + 1 }} / {{ totalTables }}
{{ resultSet?.name ? ', ' + resultSet?.name : '' }}
<span *ngIf="totalTables > 1 || resultSet?.name">
{{ 'TABLE' | translate }} {{ index + 1 }} / {{ totalTables }}
{{ resultSet?.name ? ', ' + resultSet?.name : '' }}
</span>
</h4>
<div *ngIf="resultSet && !resultSet.rows.length">
<mat-card appearance="outlined" class="mat-elevation-z1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export interface IAqlExecutionResponse {
q: string
columns: IAqlExecutionColumn[]
rows: any[]
name: string
name?: string
}
1 change: 1 addition & 0 deletions src/app/shared/models/feature/available-features.enum.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export enum AvailableFeatures {
SearchWithAql,
}
1 change: 1 addition & 0 deletions src/app/shared/models/feature/feature.interface.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export interface IFeature {
searchWithAql: boolean
}
17 changes: 11 additions & 6 deletions src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"MANAGER_TOOLS": "Manager Tools",
"PROFILE": "Benutzerkonto",
"SEARCH": "Suche",
"SEARCH_WITH_AQL": "Suche mit AQL",
"SIGNOUT": "Abmelden",
"SIGNIN": "Anmelden",
"USER_MANUAL": "Benutzerhandbuch"
Expand Down Expand Up @@ -115,10 +116,10 @@
"CREATE_QUERY": "Kriterium erstellen",
"CREATE_QUERY_CONTENT": "Auf dieser Seite können Sie ein Kriterium erstellen. Zum Erstellen des Kriteriums steht Ihnen ein Kriterien-Builder zur Verfügung.\n\n*Pflichtfeld",
"GENERAL_INFO_CONTENT": "Bitte geben Sie einen aussagekräftigen Titel sowie den Zweck und die Verwendung des Kriteriums ein.",
"BUILD_QUERY_CONTENT": "Erstellen Sie ein Kriterium im Kriterien-Builder. Danach können Sie das Kriterium im untenstehenden Editor bearbeiten.",
"BUILD_QUERY_CONTENT": "Erstellen Sie eine Abfrage im Abfrage-Builder. Danach können Sie die Abfrage im untenstehenden Editor bearbeiten.",
"SEARCH_QUERIES": "Kriterium suchen",
"SELECT_AT_LEAST_ONE": "Bitte wählen sie eine oder mehrere Kriterien aus",
"QUERY_BUILDER_DIALOG_HEADER": "Kriterien erstellen",
"QUERY_BUILDER_DIALOG_HEADER": "Abfrage erstellen",
"AVAILABLE_QUERIES": "Verfügbare Kriterien",
"SAVE_SUCCESS_MESSAGE": "Ihr Kriterium wurde erfolgreich erstellt.",
"SAVE_ERROR_MESSAGE": "Ihr Kriterium konnte nicht erstellt werden.",
Expand All @@ -131,7 +132,7 @@
"VALIDATION_ERROR": "Die Syntax des Kriteriums ist nicht valide",
"VALIDATION_SUCCESS": "Die Syntax des Kriteriums ist valide",
"HITS": {
"MESSAGE_SET_ALL_PARAMETERS": "Sie sollten zuerst das Kriterium definieren, um ein valides Ergebnis zu erhalten.",
"MESSAGE_SET_ALL_PARAMETERS": "Sie sollten zuerst die die Abfrage definieren, um ein valides Ergebnis zu erhalten.",
"MESSAGE_ERROR_FEW_HITS": "Übereinstimmende Patienten liegen unter der Mindestschwelle.",
"MESSAGE_ERROR_MESSAGE": "Konnte keine Patienten ermitteln. Bitte versuchen Sie es erneut."
},
Expand All @@ -147,6 +148,10 @@
"MINUTES": "Minuten",
"SECONDS": "Sekunden"
},
"SEARCH_WITH_AQL": {
"BUILD_QUERY_CONTENT": "Erstellen Sie eine Abfrage im Abfrage-Builder. Danach können Sie die Abfrage im untenstehenden Editor bearbeiten.",
"QUERY_DATA": "Ergebnisse"
},
"CONTENT_EDITOR": {
"SAVE_NAVIGATION_SUCCESS": "Die Navigationselemente wurden erfolgreich veröffentlicht.",
"SAVE_NAVIGATION_ERROR": "Die Navigationselemente konnten nicht veröffentlicht werden. Bitte überprüfen Sie Ihre Eingabe und versuchen es erneut.",
Expand Down Expand Up @@ -190,7 +195,7 @@
"CONFIGURE": "Konfigurieren",
"HITS": "Patienten",
"AVAILABLE_PROJECTS": "Verfügbare Projekte",
"BUILD_QUERY": "Kriterium erstellen",
"BUILD_QUERY": "Abfrage erstellen",
"AVAILABLE_EHR_TEMPLATES": "Verfügbare Rückgabeparameter",
"SELECT_EHR_TEMPLATE": "Rückgabeparameter auswählen",
"SELECTED_EHR_TEMPLATES": "Ausgewählte Rückgabeparameter",
Expand Down Expand Up @@ -265,7 +270,7 @@
"CLEAR_ENTRY": "Eingabe löschen",
"REQUEST_APPROVAL": "Genehmigung anfragen",
"APPLY_SELECTION": "Auswahl Übernehmen",
"FORMAT_QUERY": "Abfrage formatieren",
"FORMAT_QUERY": "Formatieren",
"VALIDATE_QUERY": "Überprüfen",
"ADD_RESEARCHERS": "Forscher hinzufügen",
"ADD_GROUP": "Gruppe hinzufügen",
Expand All @@ -279,7 +284,7 @@
"SELECT": "Auswählen",
"DESELECT": "Abwählen",
"APPROVE_USER": "Benutzer bestätigen",
"OPEN_QUERY_BUILDER": "Kriterium erstellen",
"OPEN_QUERY_BUILDER": "Builder öffnen",
"PREVIEW": "Vorschau",
"EDIT": "Bearbeiten",
"DELETE": "Löschen",
Expand Down
Loading