diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..7a73a41bf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/package.json b/package.json index 04f710ca0..5cd0af04e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "num-portal-webapp", - "version": "1.14.0", + "version": "1.15.0", "scripts": { "postinstall": "ngcc", "ng": "ng", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 91236e2ee..a3f8df620 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -19,7 +19,7 @@ import { Routes, RouterModule } from '@angular/router' import { AuthGuard } from './core/auth/guards/auth.guard' import { RoleGuard } from './core/auth/guards/role.guard' import { CanDeactivateSearchGuard } from './modules/search/can-deactivate-search.guard' -import { AvailableRoles } from './shared/models/available-roles.enum' +import { AvailableRoles, allRoles } from './shared/models/available-roles.enum' import { UserManualUrlResolver } from './shared/resolvers/usermanualurl.resolver' export const routes: Routes = [ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9502aa371..9b39688a3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -29,7 +29,6 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core' import { OAuthInterceptor } from './core/interceptors/oauth.interceptor' import { AuthService } from './core/auth/auth.service' import { DateAdapter } from '@angular/material/core' -import { CustomDatePickerAdapter } from './core/adapter/date-picker-adapter' import { MomentDateAdapter } from '@angular/material-moment-adapter' import { OAuthStorage } from 'angular-oauth2-oidc' import { WebpackTranslateLoader } from './webpack-translate-loader' diff --git a/src/app/core/constants/constants.ts b/src/app/core/constants/constants.ts index ee5d4b8b3..830a0cebf 100644 --- a/src/app/core/constants/constants.ts +++ b/src/app/core/constants/constants.ts @@ -15,3 +15,6 @@ */ export const PARAMETER_REGEX = /\$\w+/g + +export const HEALTHCHECK = 'HEALTHCHECK' +export const USERMANUAL = 'USERMANUAL' diff --git a/src/app/core/constants/navigation.ts b/src/app/core/constants/navigation.ts index 5cda773fb..52adc35af 100644 --- a/src/app/core/constants/navigation.ts +++ b/src/app/core/constants/navigation.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { AvailableRoles } from 'src/app/shared/models/available-roles.enum' +import { AvailableRoles, allRoles } from 'src/app/shared/models/available-roles.enum' import INavItem from '../../layout/models/nav-item.interface' +import { HEALTHCHECK, USERMANUAL } from './constants' export const mainNavItems: INavItem[] = [ { @@ -165,10 +166,21 @@ export const mainNavItems: INavItem[] = [ translationKey: 'NAVIGATION.MANAGER_TOOLS', roles: [AvailableRoles.Manager], }, +] +export const mainNavItemsExternal: INavItem[] = [ { - routeTo: 'user-manual', icon: 'book-open', translationKey: 'NAVIGATION.USER_MANUAL', + id: USERMANUAL, + isExternal: true, + }, + { + icon: 'file-waveform', + translationKey: 'NAVIGATION.HEALTH_CHECK', + roles: allRoles, + id: HEALTHCHECK, + isExternal: true, + highlighted: true, }, ] diff --git a/src/app/core/helper/date-helper.service.spec.ts b/src/app/core/helper/date-helper.service.spec.ts index 0d0aea8e9..15d2374e0 100644 --- a/src/app/core/helper/date-helper.service.spec.ts +++ b/src/app/core/helper/date-helper.service.spec.ts @@ -23,6 +23,7 @@ describe('DateHelperService', () => { let service: DateHelperService const dateString = '2021-01-02T11:12:13+0000' const date = new Date(2021, 0, 2, 11, 12, 13) + const momentDate = moment(date) beforeEach(() => { TestBed.configureTestingModule({}) @@ -35,7 +36,7 @@ describe('DateHelperService', () => { }) it('Should convert date times as expected', () => { - const result = DateHelperService.getIsoString(date) + const result = DateHelperService.getIsoString(momentDate) expect(result).toEqual(dateString) }) @@ -45,7 +46,7 @@ describe('DateHelperService', () => { }) it('Should convert times as expected', () => { - const result = DateHelperService.getTimeString(date) + const result = DateHelperService.getTimeString(momentDate) expect(result).toEqual('11:12:13') }) }) diff --git a/src/app/core/helper/date-helper.service.ts b/src/app/core/helper/date-helper.service.ts index f61e3bfc7..3b2f6163c 100644 --- a/src/app/core/helper/date-helper.service.ts +++ b/src/app/core/helper/date-helper.service.ts @@ -23,6 +23,9 @@ import moment from 'moment' export class DateHelperService { constructor() {} static getDateString(date: moment.Moment): string { + if (!date.isValid) { + date = moment(date) + } const d = date.toDate() const year = d.getFullYear() const month = (d.getMonth() + 1).toString().padStart(2, '0') @@ -31,16 +34,17 @@ export class DateHelperService { return `${year}-${month}-${day}` } //we switched from javascript Date to Momentjs (https://momentjs.com/). If we need to support timebased funtions the cide below needs to be changed, too - static getTimeString(date: Date): string { - const hours = date.getHours().toString().padStart(2, '0') - const minutes = date.getMinutes().toString().padStart(2, '0') - const seconds = date.getSeconds().toString().padStart(2, '0') + static getTimeString(date: moment.Moment): string { + console.log('date: ', date) + const hours = date.hours().toString().padStart(2, '0') + const minutes = date.minutes().toString().padStart(2, '0') + const seconds = date.seconds().toString().padStart(2, '0') return `${hours}:${minutes}:${seconds}` } - static getOffsetString(date: Date): string { - const offset = date.getTimezoneOffset() + static getOffsetString(date: moment.Moment): string { + const offset = date.toDate().getTimezoneOffset() const sign = offset > 0 ? '-' : '+' const hours = Math.abs(Math.trunc(offset / 60)) .toString() @@ -50,10 +54,11 @@ export class DateHelperService { return `${sign}${hours}${minutes}` } - static getIsoString(date: Date): string { - const dateString = this.getDateString(moment(date)) - const timeString = this.getTimeString(date) - const offset = this.getOffsetString(date) + static getIsoString(date: moment.Moment): string { + const momentDate = moment(date) + const dateString = this.getDateString(momentDate) + const timeString = this.getTimeString(momentDate) + const offset = this.getOffsetString(momentDate) return `${dateString}T${timeString}${offset}` } diff --git a/src/app/core/services/aql/aql.service.ts b/src/app/core/services/aql/aql.service.ts index c8730b14b..ac5843da2 100644 --- a/src/app/core/services/aql/aql.service.ts +++ b/src/app/core/services/aql/aql.service.ts @@ -75,7 +75,6 @@ export class AqlService { .subscribe((filterResult) => this.filteredAqlsSubject$.next(filterResult)) this.translateService.onLangChange.subscribe((event) => { - console.log('lang change', event.lang) this.currentLang = event.lang || 'en' this.setFilter(this.filterSet) }) diff --git a/src/app/core/services/aql/test/aql.service.spec.ts b/src/app/core/services/aql/test/aql.service.spec.ts index 3351748ca..f01fc02e6 100644 --- a/src/app/core/services/aql/test/aql.service.spec.ts +++ b/src/app/core/services/aql/test/aql.service.spec.ts @@ -59,7 +59,6 @@ describe('AqlService', () => { userProfileObservable$: userProfileSubject$.asObservable(), } as unknown as ProfileService - let onLanguageChangeSubject$: EventEmitter const mockTranslateService = { onLangChange: jest.fn(), currentLang: 'de', diff --git a/src/app/core/services/system-status/envs.ts b/src/app/core/services/system-status/envs.ts new file mode 100644 index 000000000..f9e72f0d3 --- /dev/null +++ b/src/app/core/services/system-status/envs.ts @@ -0,0 +1,6 @@ +export enum Environment { + DEV = 'DEV', + TEST = 'TEST', + PREPROD = 'PREPROD', + STAGING = 'STAGING', +} diff --git a/src/app/core/services/system-status/system-status.interface.ts b/src/app/core/services/system-status/system-status.interface.ts new file mode 100644 index 000000000..f2fc1d277 --- /dev/null +++ b/src/app/core/services/system-status/system-status.interface.ts @@ -0,0 +1,8 @@ +export interface SystemStatus { + EHRBASE: string + FE: string + FHIR_BRIDGE: string + KEYCLOAK: string + NUM: string + CHECK_FOR_ANNOUNCEMENTS: string +} diff --git a/src/app/core/services/system-status/system-status.service.spec.ts b/src/app/core/services/system-status/system-status.service.spec.ts new file mode 100644 index 000000000..6dd40c052 --- /dev/null +++ b/src/app/core/services/system-status/system-status.service.spec.ts @@ -0,0 +1,58 @@ +import { TestBed } from '@angular/core/testing' + +import { SystemStatusService } from './system-status.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { AppConfigService } from 'src/app/config/app-config.service' +import { HttpClient } from '@angular/common/http' + +describe('SystemStatusService', () => { + let service: SystemStatusService + + const appConfig = { + config: { api: { baseUrl: 'foo.bar' } }, + } as unknown as AppConfigService + + const httpService = { + get: { + EHRBASE: 'string', + FE: 'string', + FHIR_BRIDGE: 'string', + KEYCLOAK: 'string', + NUM: 'string', + }, + } as unknown as HttpClient + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { + provide: AppConfigService, + useValue: appConfig, + }, + { + provide: HttpClient, + useValue: httpService, + }, + ], + }) + service = TestBed.inject(SystemStatusService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) + it('should check system status', () => { + service.getSystemStatusOberservable() + }) + it('should check for errors', () => { + service.hasError({ + EHRBASE: '', + KEYCLOAK: '', + NUM: '', + CHECK_FOR_ANNOUNCEMENTS: '', + FE: '', + FHIR_BRIDGE: '', + }) + }) +}) diff --git a/src/app/core/services/system-status/system-status.service.ts b/src/app/core/services/system-status/system-status.service.ts new file mode 100644 index 000000000..524b413d6 --- /dev/null +++ b/src/app/core/services/system-status/system-status.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core' +import { Environment } from './envs' +import { HttpClient } from '@angular/common/http' +import { AppConfigService } from 'src/app/config/app-config.service' +import { SystemStatus } from './system-status.interface' +import { Observable } from 'rxjs' + +@Injectable({ + providedIn: 'root', +}) +export class SystemStatusService { + baseUrl: string + constructor(private httpClient: HttpClient, public appConfig: AppConfigService) { + this.baseUrl = `${appConfig.config.api.baseUrl}/admin/services-status` + } + + private getEnv(): Environment { + const hostedEnv = window.location.host.split('.')[0] + let env: Environment + if (hostedEnv.includes('dev') || hostedEnv.includes('localhost')) { + env = Environment.DEV + } else if (hostedEnv.includes('test')) { + env = Environment.TEST + } else if (hostedEnv.includes('preprod')) { + env = Environment.PREPROD + } else { + env = Environment.STAGING + } + return env + } + /* getSystemStatus(): Promise { + return new Promise((resolve, reject) => { + this.httpClient.get(`${this.baseUrl}?setup=${this.getEnv()}`).subscribe((systemStatus) => { + resolve(systemStatus as SystemStatus) + }) + }) + } */ + getSystemStatusOberservable(): Observable { + return new Observable((subscriber) => { + this.httpClient.get(`${this.baseUrl}?setup=${this.getEnv()}`).subscribe((systemStatus) => { + subscriber.next(systemStatus as SystemStatus) + }) + }) + } + hasError(status: SystemStatus): boolean { + return ( + status.EHRBASE !== '' || + status.FE !== '' || + status.FHIR_BRIDGE !== '' || + status.KEYCLOAK !== '' || + status.NUM !== '' || + status.CHECK_FOR_ANNOUNCEMENTS !== '' + ) + } +} diff --git a/src/app/core/utils/value-converter.utils.ts b/src/app/core/utils/value-converter.utils.ts index 8edc93268..fe511b096 100644 --- a/src/app/core/utils/value-converter.utils.ts +++ b/src/app/core/utils/value-converter.utils.ts @@ -29,10 +29,10 @@ export const convertParameterInputToType = ( outputValue = DateHelperService.getDateString(inputValue as Moment) break case AqlParameterValueType.Time: - outputValue = DateHelperService.getTimeString(inputValue as Date) + outputValue = DateHelperService.getTimeString(inputValue as Moment) break case AqlParameterValueType.DateTime: - outputValue = DateHelperService.getIsoString(inputValue as Date) + outputValue = DateHelperService.getIsoString(inputValue as Moment) break case AqlParameterValueType.Number: outputValue = parseInt(inputValue as string, 10) diff --git a/src/app/layout/components/side-menu/side-menu.component.html b/src/app/layout/components/side-menu/side-menu.component.html index 804e7b0bc..c2821b90e 100644 --- a/src/app/layout/components/side-menu/side-menu.component.html +++ b/src/app/layout/components/side-menu/side-menu.component.html @@ -39,6 +39,18 @@
{{ item.translationKey | translate }}
+ + + +
{{ item.translationKey | translate }}
+
+
diff --git a/src/app/layout/components/side-menu/side-menu.component.scss b/src/app/layout/components/side-menu/side-menu.component.scss index 337df2367..da963bf76 100644 --- a/src/app/layout/components/side-menu/side-menu.component.scss +++ b/src/app/layout/components/side-menu/side-menu.component.scss @@ -23,3 +23,6 @@ fa-icon { justify-content: center; align-items: center; } +.num-mat-list-item.highlighted { + color: #eb586a; +} diff --git a/src/app/layout/components/side-menu/side-menu.component.spec.ts b/src/app/layout/components/side-menu/side-menu.component.spec.ts index f85d926ff..f766c82d0 100644 --- a/src/app/layout/components/side-menu/side-menu.component.spec.ts +++ b/src/app/layout/components/side-menu/side-menu.component.spec.ts @@ -19,7 +19,7 @@ import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testi import { SideMenuComponent } from './side-menu.component' import { MaterialModule } from '../../material/material.module' import { RouterTestingModule } from '@angular/router/testing' -import { TranslateModule } from '@ngx-translate/core' +import { TranslateModule, TranslateService } from '@ngx-translate/core' import { DirectivesModule } from 'src/app/shared/directives/directives.module' import { AuthService } from 'src/app/core/auth/auth.service' import { OAuthService } from 'angular-oauth2-oidc' @@ -29,6 +29,10 @@ import { mockNavigationLinks } from '../../../../mocks/data-mocks/navigation-lin import { DialogService } from 'src/app/core/services/dialog/dialog.service' import { COOKIE_DIALOG_CONFIG } from './constants' import { Component } from '@angular/core' +import { AppConfigService } from 'src/app/config/app-config.service' +import { HEALTHCHECK, USERMANUAL } from 'src/app/core/constants/constants' +import { SystemStatusService } from 'src/app/core/services/system-status/system-status.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' describe('SideMenuComponent', () => { let component: SideMenuComponent @@ -44,6 +48,14 @@ describe('SideMenuComponent', () => { initCodeFlow: () => {}, } as OAuthService + const systemStatusService = { + getSystemStatusOberservable: jest.fn(), + } as unknown as SystemStatusService + + const appConfig = { + config: { api: { baseUrl: 'foo.bar' } }, + } as unknown as AppConfigService + const mockContentService = { getNavigationLinks: jest.fn(), } as unknown as ContentService @@ -84,6 +96,7 @@ describe('SideMenuComponent', () => { ]), TranslateModule.forRoot(), DirectivesModule, + HttpClientTestingModule, ], providers: [ { @@ -102,6 +115,10 @@ describe('SideMenuComponent', () => { provide: DialogService, useValue: mockDialogService, }, + { + provide: AppConfigService, + useValue: appConfig, + }, ], }).compileComponents() }) @@ -112,10 +129,36 @@ describe('SideMenuComponent', () => { .spyOn(mockContentService, 'getNavigationLinks') .mockImplementation(() => of(mockNavigationLinks)) component = fixture.componentInstance + component.manualUrl = { DE: 'foo', EN: 'bar' } + component.mainNavItemsExternal = [ + { + icon: 'book-open', + translationKey: 'NAVIGATION.USER_MANUAL', + id: USERMANUAL, + isExternal: true, + }, + { + icon: 'file-waveform', + translationKey: 'NAVIGATION.HEALTH_CHECK', + id: HEALTHCHECK, + isExternal: true, + highlighted: true, + }, + ] fixture.detectChanges() jest.spyOn(component.toggleSideMenu, 'emit') jest.spyOn(authService, 'logout').mockImplementation() jest.spyOn(authService, 'login').mockImplementation() + jest.spyOn(systemStatusService, 'getSystemStatusOberservable').mockImplementation(() => + of({ + EHRBASE: 'string', + FE: 'string', + FHIR_BRIDGE: 'string', + KEYCLOAK: 'string', + NUM: 'string', + CHECK_FOR_ANNOUNCEMENTS: 'string', + }) + ) jest.clearAllMocks() }) @@ -129,6 +172,7 @@ describe('SideMenuComponent', () => { icon: 'test', routeTo: 'test', translationKey: 'test', + isExternal: false, }, ] userInfoSubject$.next(userInfo) @@ -136,7 +180,68 @@ describe('SideMenuComponent', () => { const nativeElement = fixture.debugElement.nativeElement const button = nativeElement.querySelector('.mat-list-item') button.click() - expect(component.toggleSideMenu.emit).toHaveBeenCalled() + expect(component.toggleSideMenu.emit).toHaveBeenCalledTimes(1) + }) + + it('should handle the system status', () => { + component.handleSystemStatus() + }) + + it('should navigate to dynamic healthcheck url', () => { + const navItem = { + icon: 'test', + routeTo: 'test', + translationKey: 'test', + isExternal: true, + id: HEALTHCHECK, + } + component.mainNavItems = null + component.secondaryNavItems = [navItem] + fixture.detectChanges() + const nativeElement = fixture.debugElement.nativeElement + const button = nativeElement.querySelector( + `[data-test="side-menu__secondary-nav__${navItem.translationKey}"]` + ) as HTMLElement + button.click() + fixture.detectChanges() + }) + it('should navigate to dynamic user manual url (german)', () => { + component.currentLang = 'de' + const navItem = { + icon: 'test', + routeTo: 'test', + translationKey: 'test', + isExternal: true, + id: USERMANUAL, + } + component.mainNavItems = null + component.secondaryNavItems = [navItem] + fixture.detectChanges() + const nativeElement = fixture.debugElement.nativeElement + const button = nativeElement.querySelector( + `[data-test="side-menu__secondary-nav__${navItem.translationKey}"]` + ) as HTMLElement + button.click() + fixture.detectChanges() + }) + it('should navigate to dynamic user manual url (english)', () => { + component.currentLang = 'en' + const navItem = { + icon: 'test', + routeTo: 'test', + translationKey: 'test', + isExternal: true, + id: USERMANUAL, + } + component.mainNavItems = null + component.secondaryNavItems = [navItem] + fixture.detectChanges() + const nativeElement = fixture.debugElement.nativeElement + const button = nativeElement.querySelector( + `[data-test="side-menu__secondary-nav__${navItem.translationKey}"]` + ) as HTMLElement + button.click() + fixture.detectChanges() }) it('Calls logout function when logout button is clicked', () => { @@ -144,6 +249,7 @@ describe('SideMenuComponent', () => { icon: 'test', routeTo: '#logout', translationKey: 'test', + isExternal: false, } component.mainNavItems = null component.secondaryNavItems = [navItem] @@ -154,7 +260,6 @@ describe('SideMenuComponent', () => { const button = nativeElement.querySelector( `[data-test="side-menu__secondary-nav__${navItem.translationKey}"]` ) as HTMLElement - button.click() fixture.detectChanges() expect(authService.logout).toHaveBeenCalled() @@ -166,6 +271,7 @@ describe('SideMenuComponent', () => { icon: 'test', routeTo: '#login', translationKey: 'test', + isExternal: false, } beforeEach(() => { component.mainNavItems = null diff --git a/src/app/layout/components/side-menu/side-menu.component.ts b/src/app/layout/components/side-menu/side-menu.component.ts index db9eb3856..836be69f0 100644 --- a/src/app/layout/components/side-menu/side-menu.component.ts +++ b/src/app/layout/components/side-menu/side-menu.component.ts @@ -19,6 +19,7 @@ import { routes } from '../../../app-routing.module' import INavItem from '../../models/nav-item.interface' import { mainNavItems, + mainNavItemsExternal, secondaryNavItemsLoggedIn, secondaryNavItemsLoggedOut, } from '../../../core/constants/navigation' @@ -27,6 +28,11 @@ import { Subscription } from 'rxjs' import { ContentService } from '../../../core/services/content/content.service' import { DialogService } from 'src/app/core/services/dialog/dialog.service' import { COOKIE_DIALOG_CONFIG } from './constants' +import { HttpClient } from '@angular/common/http' +import { AppConfigService } from 'src/app/config/app-config.service' +import { HEALTHCHECK, USERMANUAL } from 'src/app/core/constants/constants' +import { TranslateService } from '@ngx-translate/core' +import { SystemStatusService } from 'src/app/core/services/system-status/system-status.service' @Component({ selector: 'num-side-menu', @@ -37,32 +43,54 @@ export class SideMenuComponent implements OnInit, OnDestroy { private subscriptions = new Subscription() routes = routes mainNavItems = mainNavItems + mainNavItemsExternal = mainNavItemsExternal secondaryNavItems: INavItem[] + baseUrl: string isLoggedIn = false + healthCheckUrl: string + manualUrl: any + currentLang = 'de' + @Output() toggleSideMenu = new EventEmitter() constructor( private authService: AuthService, public contentService: ContentService, - private dialogService: DialogService + private dialogService: DialogService, + private httpClient: HttpClient, + private appConfig: AppConfigService, + public translateService: TranslateService, + private systemService: SystemStatusService ) {} ngOnInit(): void { this.subscriptions.add( this.authService.userInfoObservable$.subscribe(() => this.handleUserInfo()) ) + this.getDynamicExternalURLs() mainNavItems.forEach((item) => { const roles = routes.filter((route) => route.path === item.routeTo)[0].data?.roles item.roles = roles }) + this.handleSystemStatus() } ngOnDestroy(): void { this.subscriptions.unsubscribe() } + handleSystemStatus(): void { + this.systemService.getSystemStatusOberservable().subscribe((status) => { + this.mainNavItemsExternal.forEach((item) => { + if (item.id === HEALTHCHECK) { + item.highlighted = this.systemService.hasError(status) + } + }) + }) + } + handleUserInfo(): void { if (this.authService.isLoggedIn) { this.contentService.getNavigationLinks().subscribe() @@ -80,11 +108,42 @@ export class SideMenuComponent implements OnInit, OnDestroy { } else if (item?.routeTo === '#login') { this.handleLoginWithDialog() } + // handle dynamic external urls + if (item && item.isExternal) { + let lang: string + switch (item.id) { + case HEALTHCHECK: + window.open(this.healthCheckUrl, '_blank') + break + case USERMANUAL: + if (!this.translateService || !this.translateService.currentLang) { + lang = this.currentLang + } else { + lang = this.translateService.currentLang + } + /* if (this.translateService.currentLang == 'de') { */ + if (lang == 'de') { + window.open(this.manualUrl.DE, '_blank') + /* } else if (this.translateService.currentLang == 'en') { */ + } else if (lang == 'en') { + window.open(this.manualUrl.EN, '_blank') + } + } + } const target = $event.currentTarget as HTMLElement target.blur() this.toggleSideMenu.emit() } + getDynamicExternalURLs(): void { + this.httpClient + .get(`${this.appConfig.config.api.baseUrl}/admin/external-urls`) + .subscribe((response: any) => { + this.healthCheckUrl = response.systemStatusUrl + this.manualUrl = response.userManualUrl + }) + } + handleLoginWithDialog(): void { const dialogRef = this.dialogService.openDialog(COOKIE_DIALOG_CONFIG) dialogRef.afterClosed().subscribe((confirmResult: boolean | undefined) => { diff --git a/src/app/layout/custom-icons/file-waveform.ts b/src/app/layout/custom-icons/file-waveform.ts new file mode 100644 index 000000000..8056d8aec --- /dev/null +++ b/src/app/layout/custom-icons/file-waveform.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2021 Vitagroup AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const prefix = 'fas' +const iconName = 'file-waveform' +const width = 512 +const height = 512 +const ligatures = [] +const unicode = null +const path = + 'M96 0C60.7 0 32 28.7 32 64V288H144c6.1 0 11.6 3.4 14.3 8.8L176 332.2l49.7-99.4c2.7-5.4 8.3-8.8 14.3-8.8s11.6 3.4 14.3 8.8L281.9 288H352c8.8 0 16 7.2 16 16s-7.2 16-16 16H272c-6.1 0-11.6-3.4-14.3-8.8L240 275.8l-49.7 99.4c-2.7 5.4-8.3 8.8-14.3 8.8s-11.6-3.4-14.3-8.8L134.1 320H32V448c0 35.3 28.7 64 64 64H352c35.3 0 64-28.7 64-64V160H288c-17.7 0-32-14.3-32-32V0H96zM288 0V128H416L288 0z' +export const fileWaveform = { + prefix, + iconName, + icon: [width, height, ligatures, unicode, path], +} as any diff --git a/src/app/layout/custom-icons/index.ts b/src/app/layout/custom-icons/index.ts index 2f62c02ca..60284eb9c 100644 --- a/src/app/layout/custom-icons/index.ts +++ b/src/app/layout/custom-icons/index.ts @@ -16,5 +16,6 @@ import { buildingLock } from './building-lock' import { numWelcome } from './num-welcome' +import { fileWaveform } from './file-waveform' -export const CUSTOM_ICONS = [numWelcome, buildingLock] +export const CUSTOM_ICONS = [numWelcome, buildingLock, fileWaveform] diff --git a/src/app/layout/models/nav-item.interface.ts b/src/app/layout/models/nav-item.interface.ts index 434024de1..9fea760c2 100644 --- a/src/app/layout/models/nav-item.interface.ts +++ b/src/app/layout/models/nav-item.interface.ts @@ -17,11 +17,13 @@ import { AvailableRoles } from 'src/app/shared/models/available-roles.enum' export default interface INavItem { - routeTo: string + routeTo?: string icon?: string | string[] translationKey: string tabNav?: INavItem[] id?: string roles?: AvailableRoles[] disabled?: boolean + isExternal?: boolean + highlighted?: boolean } diff --git a/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.html b/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.html index 0462b2234..7f6a8bfcc 100644 --- a/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.html +++ b/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.html @@ -34,13 +34,26 @@

{{ 'QUERY_CATEGORIES.TABLE_TITLE' | translate }}< + + + diff --git a/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.ts b/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.ts index 5b8e0ae9e..fae19f5d9 100644 --- a/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.ts +++ b/src/app/modules/aql-category/components/aql-categories-table/aql-categories-table.component.ts @@ -70,10 +70,8 @@ export class AqlCategoriesTableComponent ngOnInit() { this.pageIndex = 0 - this.sortBy = 'name-en' this.sortDir = 'ASC' - this.getAll() } diff --git a/src/app/modules/data-explorer/components/data-explorer/data-explorer.component.html b/src/app/modules/data-explorer/components/data-explorer/data-explorer.component.html index c841eba3c..02c430774 100644 --- a/src/app/modules/data-explorer/components/data-explorer/data-explorer.component.html +++ b/src/app/modules/data-explorer/components/data-explorer/data-explorer.component.html @@ -33,9 +33,9 @@

{{ 'DATA_EXPLORER.EXPLORE_PROJECT' | translate }}

>
-

{{ 'DATA_EXPLORER.HEADLINE_RETREIVE_SECTION' | translate }}

+

{{ 'DATA_EXPLORER.HEADLINE_RETRIEVE_SECTION' | translate }}

- {{ 'DATA_EXPLORER.INTRODUCTION_RETREIVE_SECTION' | translate }} + {{ 'DATA_EXPLORER.INTRODUCTION_RETRIEVE_SECTION' | translate }}

diff --git a/src/app/modules/organization-management/components/organization-management/organization-management.component.ts b/src/app/modules/organization-management/components/organization-management/organization-management.component.ts index dc7cace90..6aab1ba6b 100644 --- a/src/app/modules/organization-management/components/organization-management/organization-management.component.ts +++ b/src/app/modules/organization-management/components/organization-management/organization-management.component.ts @@ -22,7 +22,6 @@ import { Subscription } from 'rxjs' import { DEFAULT_ORGANIZATION_FILTER } from 'src/app/core/constants/default-filter-organization' import { OrganizationsTableComponent } from '../organizations-table/organizations-table.component' import { OrganizationUserFilterChipId } from 'src/app/shared/models/organization/organization-filter-chip.enum' -import { forEach } from 'lodash' @Component({ selector: 'num-organization-management', diff --git a/src/app/modules/organization-management/components/organizations-table/organizations-table.component.ts b/src/app/modules/organization-management/components/organizations-table/organizations-table.component.ts index ddfdbc636..98507f8a7 100644 --- a/src/app/modules/organization-management/components/organizations-table/organizations-table.component.ts +++ b/src/app/modules/organization-management/components/organizations-table/organizations-table.component.ts @@ -88,7 +88,6 @@ export class OrganizationsTableComponent } getAll() { - console.log('PI: ', this.pageIndex) this.subscriptions.add( this.organizationService .getAllPag(this.pageIndex, this.pageSize, this.active, this.sortDir, this.sortBy) diff --git a/src/app/modules/projects/components/project-editor-accordion/project-editor-accordion.component.ts b/src/app/modules/projects/components/project-editor-accordion/project-editor-accordion.component.ts index a45df2efd..299a65801 100644 --- a/src/app/modules/projects/components/project-editor-accordion/project-editor-accordion.component.ts +++ b/src/app/modules/projects/components/project-editor-accordion/project-editor-accordion.component.ts @@ -34,7 +34,7 @@ export class ProjectEditorAccordionComponent { @Input() isResearchersDisabled: boolean @Input() isGeneralInfoDisabled: boolean @Input() isCohortBuilderDisabled: boolean - + @Input() isUserProjectAdmin: boolean @Input() project: ProjectUiModel @Input() projectForm: FormGroup @Input() cohortGroup: CohortGroupUiModel diff --git a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.html b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.html index 7829413c4..d7e0065d0 100644 --- a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.html +++ b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.html @@ -130,7 +130,7 @@ projectStatus === possibleStatus.Draft || projectStatus === possibleStatus.ChangeRequest || projectStatus === possibleStatus.Approved - ) + ) || !isUserProjectAdmin " *numUserHasRole="[availableRoles.StudyCoordinator]" data-test="project-editor__buttons__start-edit-button" diff --git a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.spec.ts b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.spec.ts index 093b50eaa..ba56e3704 100644 --- a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.spec.ts +++ b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.spec.ts @@ -106,17 +106,17 @@ describe('ProjectEditorButtonsComponent', () => { const previewCases = [ { status: ProjectStatus.Draft, - disabled: false, + disabled: true, text: 'BUTTON.EDIT', }, { status: ProjectStatus.ChangeRequest, - disabled: false, + disabled: true, text: 'BUTTON.EDIT', }, { status: ProjectStatus.Approved, - disabled: false, + disabled: true, text: 'BUTTON.EDIT_RESEARCHERS', }, { @@ -145,7 +145,6 @@ describe('ProjectEditorButtonsComponent', () => { text: 'BUTTON.EDIT', }, ] - test.each(previewCases)('shoud behave as expected', (testcase) => { component.projectStatus = testcase.status fixture.detectChanges() diff --git a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.ts b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.ts index 80d1b3f64..6e7bbddea 100644 --- a/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.ts +++ b/src/app/modules/projects/components/project-editor-buttons/project-editor-buttons.component.ts @@ -38,9 +38,11 @@ export class ProjectEditorButtonsComponent { @Input() isResearchersDefined: boolean @Input() isTemplatesDefined: boolean @Input() isCohortDefined: boolean + @Input() isCohortValid: boolean @Input() approverForm: FormGroup @Input() isExportLoading: boolean @Input() isSavedProject: boolean + @Input() isUserProjectAdmin: boolean @Output() saveAll = new EventEmitter() @Output() saveResearchers = new EventEmitter() diff --git a/src/app/modules/projects/components/project-editor/project-editor.component.creation.spec.ts b/src/app/modules/projects/components/project-editor/project-editor.component.creation.spec.ts index b11c90b6d..c8763cf4a 100644 --- a/src/app/modules/projects/components/project-editor/project-editor.component.creation.spec.ts +++ b/src/app/modules/projects/components/project-editor/project-editor.component.creation.spec.ts @@ -44,6 +44,8 @@ import { AqlUiModel } from 'src/app/shared/models/aql/aql-ui.model' import { mockAql1, mockAql3 } from 'src/mocks/data-mocks/aqls.mock' import { IDetermineHits } from 'src/app/shared/components/editor-determine-hits/determine-hits.interface' import { HttpErrorResponse } from '@angular/common/http' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { ProfileService } from 'src/app/core/services/profile/profile.service' describe('ProjectEditorComponent On Creation', () => { let component: ProjectEditorComponent @@ -84,11 +86,16 @@ describe('ProjectEditorComponent On Creation', () => { openToast: jest.fn(), } as unknown as ToastMessageService + const profileService = { + apiBase: jest.fn(), + get: jest.fn(), + } + @Component({ selector: 'num-project-editor-accordion', template: '' }) class StubProjectEditorAccordionComponent { @Input() isResearchersFetched: boolean @Input() isCohortsFetched: boolean - + @Input() isUserProjectAdmin: boolean @Input() isTemplatesDisabled: boolean @Input() isResearchersDisabled: boolean @Input() isGeneralInfoDisabled: boolean @@ -160,6 +167,7 @@ describe('ProjectEditorComponent On Creation', () => { MaterialModule, ReactiveFormsModule, FontAwesomeTestingModule, + HttpClientTestingModule, TranslateModule.forRoot(), RouterTestingModule.withRoutes([{ path: '**', redirectTo: '' }]), ], @@ -184,12 +192,31 @@ describe('ProjectEditorComponent On Creation', () => { provide: ToastMessageService, useValue: mockToastMessageService, }, + { + provide: ProfileService, + useValue: profileService, + }, ], }).compileComponents() }) beforeEach(() => { jest.spyOn(mockToastMessageService, 'openToast').mockImplementation() + jest.spyOn(profileService, 'apiBase').mockReturnValue('something') + jest.spyOn(profileService, 'get').mockImplementation(() => { + return of({ + id: 'string', + username: 'string', + firstName: 'string', + lastName: 'string', + email: 'string', + createdTimestamp: 1, + roles: null, + approved: true, + organization: null, + }) + }) + TestBed.inject(Router) fixture = TestBed.createComponent(ProjectEditorComponent) component = fixture.componentInstance diff --git a/src/app/modules/projects/components/project-editor/project-editor.component.edit.spec.ts b/src/app/modules/projects/components/project-editor/project-editor.component.edit.spec.ts index 9be2f03f0..6b2578e80 100644 --- a/src/app/modules/projects/components/project-editor/project-editor.component.edit.spec.ts +++ b/src/app/modules/projects/components/project-editor/project-editor.component.edit.spec.ts @@ -56,12 +56,18 @@ jest.mock('src/app/core/utils/download-file.utils', () => ({ downloadFile: jest.fn().mockImplementation(() => ''), })) import { downloadFile } from 'src/app/core/utils/download-file.utils' +import { ProfileService } from 'src/app/core/services/profile/profile.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' describe('ProjectEditorComponent', () => { let component: ProjectEditorComponent let fixture: ComponentFixture let router: Router + const mockProfileService = { + get: jest.fn(), + } as unknown as ProfileService + const projectService = { create: jest.fn(), update: jest.fn(), @@ -179,6 +185,7 @@ describe('ProjectEditorComponent', () => { ProjectEditorApprovalStubComponent, ], imports: [ + HttpClientTestingModule, BrowserAnimationsModule, MaterialModule, ReactiveFormsModule, @@ -211,6 +218,10 @@ describe('ProjectEditorComponent', () => { provide: ToastMessageService, useValue: mockToast, }, + { + provide: ProfileService, + useValue: mockProfileService, + }, ], }).compileComponents() }) @@ -229,6 +240,19 @@ describe('ProjectEditorComponent', () => { jest.spyOn(projectService, 'update').mockImplementation(() => of(mockProject1)) jest.spyOn(projectService, 'updateStatusById').mockImplementation(() => of(mockProject1)) jest.spyOn(projectService, 'exportPrint').mockImplementation(() => of('')) + jest.spyOn(mockProfileService, 'get').mockImplementation(() => + of({ + id: 'string', + username: 'string', + firstName: 'string', + lastName: 'string', + email: 'string', + createdTimestamp: 1, + roles: null, + approved: true, + organization: null, + }) + ) }) describe('When the components gets initialized and the cohortId is not specified', () => { diff --git a/src/app/modules/projects/components/project-editor/project-editor.component.html b/src/app/modules/projects/components/project-editor/project-editor.component.html index c82cfad42..493421efe 100644 --- a/src/app/modules/projects/components/project-editor/project-editor.component.html +++ b/src/app/modules/projects/components/project-editor/project-editor.component.html @@ -54,6 +54,7 @@

{{ 'NAVIGATION.DEFINE_PROJECT' | translate }}

{ + this.isUserProjectAdmin = user.id === this.project.coordinator.id + }) } updateDetermineHits(count?: number | null, message?: string, isLoading = false): void { diff --git a/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.spec.ts b/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.spec.ts index 5bb9fec39..e7f25bfb3 100644 --- a/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.spec.ts +++ b/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.spec.ts @@ -104,7 +104,31 @@ describe('ApprovedUsersTableComponent', () => { describe('When filter type and search is triggered', () => { it('should filter and search', () => { jest.spyOn(adminService, 'getAllPag').mockReturnValue(of({})) - component.initSearchAndFilters(false, 'testSearch') + const filter = { + filterItem: [ + { + id: 'USER.ALL_USERS', + title: 'FILTER_CHIP.ALL', + isSelected: true, + }, + { + id: 'USER.ORGANIZATION_USERS', + title: 'FILTER_CHIP.ORGANIZATION', + isSelected: false, + }, + { + id: 'USER_MANAGEMENT.ACTIVE', + title: 'USER_MANAGEMENT.ACTIVE', + isSelected: false, + }, + { + id: 'USER_MANAGEMENT.INACTIVE', + title: 'USER_MANAGEMENT.INACTIVE', + isSelected: false, + }, + ], + } + component.initSearchAndFilters(filter, 'testSearch') expect(component.filters.search).toEqual('testSearch') }) }) diff --git a/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.ts b/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.ts index 6028cce64..830259e79 100644 --- a/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.ts +++ b/src/app/modules/user-management/components/approved-users-table/approved-users-table.component.ts @@ -28,6 +28,7 @@ import { ApprovedUsersTableColumn } from 'src/app/shared/models/user/approved-ta import { SortableTable } from 'src/app/shared/models/sortable-table.model' import { MatDialogRef } from '@angular/material/dialog' import { MatPaginator } from '@angular/material/paginator' +import { forEach } from 'lodash' @Component({ selector: 'num-approved-users-table', @@ -77,7 +78,7 @@ export class ApprovedUsersTableComponent extends SortableTable implements approved: true, search: null, type: null, - enabled: null, + enabled: true, } this.sortBy = 'firstName' @@ -93,7 +94,6 @@ export class ApprovedUsersTableComponent extends SortableTable implements if (returnFirstIndex && typeof this.paginator !== 'undefined') { this.goToFirstPage() } - this.subscriptions.add( this.adminService .getAllPag(this.pageIndex, this.pageSize, this.sortDir, this.sortBy, this.filters) @@ -130,7 +130,13 @@ export class ApprovedUsersTableComponent extends SortableTable implements } initSearchAndFilters(filter, search) { - this.handleFilterChange(filter, '', true) + let selectedTab = '' + filter.filterItem.forEach((filterItem) => { + if (filterItem.isSelected) { + selectedTab = filterItem.title + } + }) + this.handleFilterChange(null, selectedTab, true) this.handleSearchChange(search, true) this.getAll(true) @@ -152,22 +158,18 @@ export class ApprovedUsersTableComponent extends SortableTable implements } handleFilterChange(isOrg: any, selectedTab: any, noGet = false): void { - if (isOrg) { + if (selectedTab.includes('ORGANIZATION')) { + this.filters.type = 'ORGANIZATION' + this.filters.enabled = null + } else if (selectedTab.includes('ALL')) { this.filters.type = null this.filters.enabled = null - } else { - if (selectedTab.includes('INACTIVE')) { - this.filters.enabled = false - this.filters.type = null - } else { - if (selectedTab.includes('ACTIVE')) { - this.filters.enabled = true - this.filters.type = null - } else { - this.filters.type = 'ORGANIZATION' - this.filters.enabled = null - } - } + } else if (selectedTab.includes('INACTIVE')) { + this.filters.type = null + this.filters.enabled = false + } else if (selectedTab.includes('ACTIVE')) { + this.filters.type = null + this.filters.enabled = true } if (!noGet) { diff --git a/src/app/modules/user-management/components/approved-users/approved-users.component.ts b/src/app/modules/user-management/components/approved-users/approved-users.component.ts index 20e9da692..35ec3535c 100644 --- a/src/app/modules/user-management/components/approved-users/approved-users.component.ts +++ b/src/app/modules/user-management/components/approved-users/approved-users.component.ts @@ -45,7 +45,8 @@ export class ApprovedUsersComponent implements OnInit, OnDestroy { this.filterConfig = config setTimeout(() => { this.table.initSearchAndFilters( - this.filterConfig.filterItem[0].isSelected, + //this.filterConfig.filterItem[0].isSelected, + this.filterConfig, this.filterConfig.searchText ) }) diff --git a/src/app/shared/components/time-input/time-input.component.ts b/src/app/shared/components/time-input/time-input.component.ts index 3df616179..4aca9b53b 100644 --- a/src/app/shared/components/time-input/time-input.component.ts +++ b/src/app/shared/components/time-input/time-input.component.ts @@ -111,11 +111,14 @@ export class TimeInputComponent implements OnInit, OnDestroy { } getValuesFromDate(value: Date): { hours; minutes; seconds } { - const hours = value.getHours() - const minutes = value.getMinutes() - const seconds = value.getSeconds() + if (value.getHours) { + const hours = value.getHours() + const minutes = value.getMinutes() + const seconds = value.getSeconds() - return { hours, minutes, seconds } + return { hours, minutes, seconds } + } + return { hours: 0, minutes: 0, seconds: 0 } } restrictToBoundaries(value: number, lower: number, upper: number): number { diff --git a/src/app/shared/constants.ts b/src/app/shared/constants.ts index 8c012b212..3b71923bc 100644 --- a/src/app/shared/constants.ts +++ b/src/app/shared/constants.ts @@ -29,3 +29,8 @@ export const USER_MANUAL_LINK = { EN: 'https://num-portal-webapp.readthedocs.io/en/latest/', DE: 'https://num-portal-webapp.readthedocs.io/de/latest/', } + +export const HEALTH_CHECK_URL = { + EN: 'https://health.num-codex.de/', + DE: 'https://health.num-codex.de/', +} diff --git a/src/app/shared/models/available-roles.enum.ts b/src/app/shared/models/available-roles.enum.ts index f014c5716..1ab8852c5 100644 --- a/src/app/shared/models/available-roles.enum.ts +++ b/src/app/shared/models/available-roles.enum.ts @@ -25,3 +25,13 @@ export enum AvailableRoles { Manager = 'MANAGER', CriteriaEditor = 'CRITERIA_EDITOR', } +export const allRoles = [ + AvailableRoles.Researcher, + AvailableRoles.StudyCoordinator, + AvailableRoles.ContentAdmin, + AvailableRoles.OrganizationAdmin, + AvailableRoles.SuperAdmin, + AvailableRoles.StudyApprover, + AvailableRoles.Manager, + AvailableRoles.CriteriaEditor, +] diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 548dcc5d6..f01c1eaa3 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -80,7 +80,8 @@ "SEARCH": "Suche", "SIGNOUT": "Abmelden", "SIGNIN": "Anmelden", - "USER_MANUAL": "Benutzerhandbuch" + "USER_MANUAL": "Benutzerhandbuch", + "HEALTH_CHECK": "Systemstatus & Meldungen" }, "PROFILE": { "EDIT_HEADER": "Benutzerkonto bearbeiten", @@ -140,7 +141,9 @@ "MESSAGE_ERROR_MESSAGE": "Konnte keine Patienten ermitteln. Bitte versuchen Sie es erneut." }, "PRIVACY_PRIVATE": "Nur mit mir", - "PRIVACY_PUBLIC": "Öffentlich" + "PRIVACY_PUBLIC": "Öffentlich", + "CATEGORY_NOT_DELETABLE": "Kategorien mit zugewiesenen Kriterien können nicht gelöscht werden." + }, "CONTENT_EDITOR": { "SAVE_NAVIGATION_SUCCESS": "Die Navigationselemente wurden erfolgreich veröffentlicht.", @@ -444,8 +447,8 @@ "RESULT_SET_ERROR": "Die Ergebnisse konnte nicht geladen werden. Bitte passen Sie die Konfiguration an und versuchen es erneut.", "EXPORT": "{{format}} Exportieren", "EXPORT_ERROR": "Fehler beim Versuch die {{format}} Datei zu exportieren, bitte versuchen Sie es später noch einmal.", - "HEADLINE_RETREIVE_SECTION": "Datenabruf", - "INTRODUCTION_RETREIVE_SECTION": "Rufen Sie Daten ab, schränken Sie die Ergebnismenge mit temporären Filtern ein oder entfernen Sie zuvor definierte temporäre Filter. Bitte beachten Sie: Bei der Filter-Definition können ungewollt Kreuzprodukte als Resultat entstehen." + "HEADLINE_RETRIEVE_SECTION": "Datenabruf", + "INTRODUCTION_RETRIEVE_SECTION": "Rufen Sie Daten ab, schränken Sie die Ergebnismenge mit temporären Filtern ein oder entfernen Sie zuvor definierte temporäre Filter. Bitte beachten Sie: Bei der Filter-Definition können ungewollt Kreuzprodukte als Resultat entstehen." }, "USER_MANAGEMENT": { "NEW_USERS_HEADER": "Alle neuen Benutzer", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9e32637ff..f32699f49 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -80,7 +80,8 @@ "SEARCH": "Search", "SIGNOUT": "Sign out", "SIGNIN": "Sign in", - "USER_MANUAL": "User Manual" + "USER_MANUAL": "User Manual", + "HEALTH_CHECK": "System Status & Messages" }, "PROFILE": { "EDIT_HEADER": "Edit User Account", @@ -140,7 +141,8 @@ "MESSAGE_ERROR_MESSAGE": "Could not determine Patients. Please try again." }, "PRIVACY_PRIVATE": "Only with me", - "PRIVACY_PUBLIC": "Public" + "PRIVACY_PUBLIC": "Public", + "CATEGORY_NOT_DELETABLE": "Categories with assigned criterias can not be deleted." }, "CONTENT_EDITOR": { "SAVE_NAVIGATION_SUCCESS": "The navigations items were successfully published.", @@ -431,7 +433,7 @@ "EXPLORE_PROJECT": "Retrieve project data", "EXPLORE_PROJECT_CONTENT": "On this page you have the possibility to retrieve the research data defined within the project.\n\nYou can either start the data retrieval directly or optionally restrict the data retrieval defined in the project even further.", "RESULT_SET": "Result Set", - "NO_RESULTS_YET": "No results yet. Please press the button 'Retreive Data' first.", + "NO_RESULTS_YET": "No results yet. Please press the button 'Retrieve Data' first.", "LOADING_RESULT_SET": "The result set is being loaded. Please wait a moment.", "EMPTY_RESULT_SET": "The configuration did not give any results. Please change the configuration and try again.", "CONFIGURATION": "Configuration", @@ -443,8 +445,8 @@ "RESULT_SET_ERROR": "The results could not be loaded. Please adjust the configuration and try again.", "EXPORT": "Export {{format}}", "EXPORT_ERROR": "Error while trying to Export the {{format}} file, please try again later.", - "HEADLINE_RETREIVE_SECTION": "Retreive Data", - "INTRODUCTION_RETREIVE_SECTION": "Retreive data, restrict the result set with filters or reset previously defined filter. Caution: Using custom filters might result in unexpected cross products." + "HEADLINE_RETRIEVE_SECTION": "Retrieve Data", + "INTRODUCTION_RETRIEVE_SECTION": "Retrieve data, restrict the result set with filters or reset previously defined filter. Caution: Using custom filters might result in unexpected cross products." }, "USER_MANAGEMENT": { "NEW_USERS_HEADER": "All New Users",