From 8daaf1c1e0abb0164e72c535a1c4b62ad404e2ce Mon Sep 17 00:00:00 2001 From: rhenck <richard.henck@tu-berlin.de> Date: Thu, 1 Oct 2020 17:52:19 +0200 Subject: [PATCH] Improve code style --- .../status-card/status-card.component.ts | 1 - src/app/app-route-guards.ts | 12 +- src/app/app-routing.module.ts | 36 +- src/app/app.component.ts | 40 +- src/app/app.interceptor.ts | 15 +- src/app/app.interfaces.ts | 2 + src/app/app.module.spec.ts | 2 +- src/app/app.module.ts | 49 +- src/app/backend.service.spec.ts | 3 +- src/app/backend.service.ts | 78 ++-- src/app/config/app.config.ts | 9 +- src/app/group-monitor/backend.service.ts | 133 +++--- src/app/group-monitor/booklet.service.spec.ts | 128 +++--- src/app/group-monitor/booklet.service.ts | 271 ++++++----- .../group-monitor-routing.module.ts | 5 +- .../group-monitor/group-monitor.component.ts | 91 ++-- .../group-monitor/group-monitor.interfaces.ts | 135 +++--- src/app/group-monitor/group-monitor.module.ts | 87 ++-- .../test-view/test-view.component.spec.ts | 14 +- .../test-view/test-view.component.ts | 422 +++++++++--------- src/app/maindata.service.spec.ts | 2 +- src/app/maindata.service.ts | 14 +- src/app/sys-check/backend.service.ts | 5 - .../network-check/network-check.component.ts | 1 - .../sys-check/welcome/welcome.component.ts | 1 - .../workspace-admin/backend.service.spec.ts | 5 +- src/app/workspace-admin/backend.service.ts | 19 +- .../files/files.component.spec.ts | 16 +- .../workspace-admin/files/files.component.ts | 35 +- .../files/iqb-files/iqb-files.module.ts | 11 +- .../iqbFilesUploadQueue.component.ts | 149 ++++--- .../results/results.component.spec.ts | 14 +- .../results/results.component.ts | 13 +- .../syscheck/syscheck.component.spec.ts | 14 +- .../syscheck/syscheck.component.ts | 26 +- .../workspace-routing.module.ts | 16 +- .../workspace-admin/workspace.component.ts | 25 +- .../workspace-admin/workspace.interfaces.ts | 2 +- .../workspace-admin/workspace.module.spec.ts | 2 +- src/app/workspace-admin/workspace.module.ts | 23 +- .../workspacedata.service.spec.ts | 2 +- .../workspace-admin/workspacedata.service.ts | 6 +- .../workspace-monitor/backend.service.spec.ts | 3 +- src/app/workspace-monitor/backend.service.ts | 13 +- .../workspace-monitor-routing.module.ts | 5 +- .../workspace-monitor.component.ts | 112 ++--- .../workspace-monitor.module.spec.ts | 2 +- .../workspace-monitor.module.ts | 25 +- src/main.ts | 6 +- src/scripts/findCustomTexts.js | 1 + src/scripts/generateBookletConfigClass.js | 1 + src/scripts/generateTestModeClass.js | 29 +- src/test.ts | 4 +- 53 files changed, 1043 insertions(+), 1092 deletions(-) diff --git a/src/app/app-root/status-card/status-card.component.ts b/src/app/app-root/status-card/status-card.component.ts index a20864e0..4c807d1c 100644 --- a/src/app/app-root/status-card/status-card.component.ts +++ b/src/app/app-root/status-card/status-card.component.ts @@ -3,7 +3,6 @@ import {MainDataService} from '../../maindata.service'; import {AuthAccessKeyType, AuthFlagType} from '../../app.interfaces'; @Component({ - // tslint:disable-next-line:component-selector TODO rename component instead selector: 'status-card', templateUrl: './status-card.component.html' }) diff --git a/src/app/app-route-guards.ts b/src/app/app-route-guards.ts index 030b60fb..d7424117 100644 --- a/src/app/app-route-guards.ts +++ b/src/app/app-route-guards.ts @@ -1,9 +1,9 @@ -import {Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; -import {MainDataService} from './maindata.service'; -import {Observable} from 'rxjs'; -import {AuthAccessKeyType, AuthData, AuthFlagType} from './app.interfaces'; -import {BackendService} from './backend.service'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { MainDataService } from './maindata.service'; +import { Observable } from 'rxjs'; +import { AuthAccessKeyType, AuthData, AuthFlagType } from './app.interfaces'; +import { BackendService } from './backend.service'; @Injectable() export class RouteDispatcherActivateGuard implements CanActivate { diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 9d402536..21c53f0e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,21 +1,20 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import {AppRootComponent} from './app-root/app-root.component'; -import {LoginComponent} from './app-root/login/login.component'; -import {SysCheckStarterComponent} from './app-root/sys-check-starter/sys-check-starter.component'; -import {AdminStarterComponent} from './app-root/admin-starter/admin-starter.component'; -import {CodeInputComponent} from './app-root/code-input/code-input.component'; +import { AppRootComponent } from './app-root/app-root.component'; +import { LoginComponent } from './app-root/login/login.component'; +import { SysCheckStarterComponent } from './app-root/sys-check-starter/sys-check-starter.component'; +import { AdminStarterComponent } from './app-root/admin-starter/admin-starter.component'; +import { CodeInputComponent } from './app-root/code-input/code-input.component'; import { AdminComponentActivateGuard, AdminOrSuperAdminComponentActivateGuard, CodeInputComponentActivateGuard, DirectLoginActivateGuard, GroupMonitorActivateGuard, RouteDispatcherActivateGuard, SuperAdminComponentActivateGuard, TestComponentActivateGuard } from './app-route-guards'; -import {TestStarterComponent} from './app-root/test-starter/test-starter.component'; -import {RouteDispatcherComponent} from './app-root/route-dispatcher/route-dispatcher.component'; -import {PrivacyComponent} from './app-root/privacy/privacy.component'; -import {MonitorStarterComponent} from './app-root/monitor-starter/monitor-starter.component'; - +import { TestStarterComponent } from './app-root/test-starter/test-starter.component'; +import { RouteDispatcherComponent } from './app-root/route-dispatcher/route-dispatcher.component'; +import { PrivacyComponent } from './app-root/privacy/privacy.component'; +import { MonitorStarterComponent } from './app-root/monitor-starter/monitor-starter.component'; const routes: Routes = [ { @@ -78,30 +77,30 @@ const routes: Routes = [ }, { path: 'check', - loadChildren: () => import('./sys-check/sys-check.module').then(m => m.SysCheckModule) + loadChildren: () => import('./sys-check/sys-check.module').then((m) => m.SysCheckModule) }, { path: 'admin', - loadChildren: () => import('./workspace-admin/workspace.module').then(m => m.WorkspaceModule), + loadChildren: () => import('./workspace-admin/workspace.module').then((m) => m.WorkspaceModule), canActivate: [AdminComponentActivateGuard] }, { path: 'superadmin', - loadChildren: () => import('./superadmin/superadmin.module').then(m => m.SuperadminModule), + loadChildren: () => import('./superadmin/superadmin.module').then((m) => m.SuperadminModule), canActivate: [SuperAdminComponentActivateGuard] }, { path: 'wm', - loadChildren: () => import('./workspace-monitor/workspace-monitor.module').then(m => m.WorkspaceMonitorModule), + loadChildren: () => import('./workspace-monitor/workspace-monitor.module').then((m) => m.WorkspaceMonitorModule) }, { path: 'gm', - loadChildren: () => import('./group-monitor/group-monitor.module').then(m => m.GroupMonitorModule), + loadChildren: () => import('./group-monitor/group-monitor.module').then((m) => m.GroupMonitorModule) // canActivate: [GroupMonitorActivateGuard] }, { path: 't', - loadChildren: () => import('./test-controller/test-controller.module').then(m => m.TestControllerModule), + loadChildren: () => import('./test-controller/test-controller.module').then((m) => m.TestControllerModule), canActivate: [TestComponentActivateGuard] }, { @@ -114,8 +113,9 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], - providers: [RouteDispatcherActivateGuard, DirectLoginActivateGuard, CodeInputComponentActivateGuard, - AdminComponentActivateGuard, SuperAdminComponentActivateGuard, TestComponentActivateGuard, + providers: [RouteDispatcherActivateGuard, DirectLoginActivateGuard, + CodeInputComponentActivateGuard, AdminComponentActivateGuard, + SuperAdminComponentActivateGuard, TestComponentActivateGuard, AdminOrSuperAdminComponentActivateGuard ] }) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 18493e42..fb2dcb3c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,26 +1,29 @@ +import { + Component, Inject, OnDestroy, OnInit +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { CustomtextService } from 'iqb-components'; import { MainDataService } from './maindata.service'; -import {Component, Inject, OnDestroy, OnInit} from '@angular/core'; import { BackendService } from './backend.service'; -import {CustomtextService} from 'iqb-components'; -import {Subscription} from 'rxjs'; -import {AppError} from './app.interfaces'; +import { AppError } from './app.interfaces'; @Component({ selector: 'tc-root', templateUrl: './app.component.html' }) - export class AppComponent implements OnInit, OnDestroy { private appErrorSubscription: Subscription = null; + showError = false; + errorData: AppError; - constructor ( + constructor( public mds: MainDataService, private bs: BackendService, private cts: CustomtextService, - @Inject('API_VERSION_EXPECTED') private readonly expectedApiVersion: string, + @Inject('API_VERSION_EXPECTED') private readonly expectedApiVersion: string ) { } private static isValidVersion(expectedVersion: string, reportedVersion: string): boolean { @@ -53,11 +56,11 @@ export class AppComponent implements OnInit, OnDestroy { return true; } - closeErrorBox() { + closeErrorBox(): void { this.showError = false; } - ngOnInit() { + ngOnInit(): void { setTimeout(() => { this.mds.appConfig.setDefaultCustomTexts(); @@ -80,7 +83,7 @@ export class AppComponent implements OnInit, OnDestroy { this.setupFocusListeners(); - this.bs.getSysConfig().subscribe(sc => { + this.bs.getSysConfig().subscribe((sc) => { if (sc) { this.cts.addCustomTexts(sc.customTexts); const authData = MainDataService.getAuthData(); @@ -91,7 +94,7 @@ export class AppComponent implements OnInit, OnDestroy { if (!this.mds.isApiValid) { this.mds.appError$.next({ label: 'Server-Problem: API-Version ungültig', - description: 'erwartet: ' + this.expectedApiVersion + ', gefunden: ' + sc.version, + description: `erwartet: ${this.expectedApiVersion}, gefunden: ${sc.version}`, category: 'FATAL' }); } @@ -104,7 +107,7 @@ export class AppComponent implements OnInit, OnDestroy { } }); - this.bs.getSysCheckInfo().subscribe(myConfigs => { + this.bs.getSysCheckInfo().subscribe((myConfigs) => { this.mds.sysCheckAvailable = !!myConfigs; }); }); @@ -116,36 +119,33 @@ export class AppComponent implements OnInit, OnDestroy { if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support hidden = 'hidden'; visibilityChange = 'visibilitychange'; - // @ts-ignore } else if (typeof document['msHidden'] !== 'undefined') { hidden = 'msHidden'; visibilityChange = 'msvisibilitychange'; - // @ts-ignore } else if (typeof document['mozHidden'] !== 'undefined') { hidden = 'mozHidden'; visibilityChange = 'mozHidden'; - // @ts-ignore } else if (typeof document['webkitHidden'] !== 'undefined') { hidden = 'webkitHidden'; visibilityChange = 'webkitvisibilitychange'; } if (hidden && visibilityChange) { document.addEventListener(visibilityChange, () => { - this.mds.appWindowHasFocus$.next(!document[hidden]) + this.mds.appWindowHasFocus$.next(!document[hidden]); }, false); } window.addEventListener('blur', () => { - this.mds.appWindowHasFocus$.next(document.hasFocus()) + this.mds.appWindowHasFocus$.next(document.hasFocus()); }); window.addEventListener('focus', () => { - this.mds.appWindowHasFocus$.next(document.hasFocus()) + this.mds.appWindowHasFocus$.next(document.hasFocus()); }); window.addEventListener('unload', () => { - this.mds.appWindowHasFocus$.next(!document[hidden]) + this.mds.appWindowHasFocus$.next(!document[hidden]); }); } - ngOnDestroy() { + ngOnDestroy(): void { if (this.appErrorSubscription !== null) { this.appErrorSubscription.unsubscribe(); } diff --git a/src/app/app.interceptor.ts b/src/app/app.interceptor.ts index a97d0120..453080f3 100644 --- a/src/app/app.interceptor.ts +++ b/src/app/app.interceptor.ts @@ -1,13 +1,13 @@ -import { MainDataService } from './maindata.service'; import { Injectable } from '@angular/core'; +import { Router, RouterState, RouterStateSnapshot } from '@angular/router'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'; -import {Observable, throwError} from 'rxjs'; -import {catchError} from 'rxjs/operators'; -import {Router, RouterState, RouterStateSnapshot} from '@angular/router'; -import {ApiError} from './app.interfaces'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { MainDataService } from './maindata.service'; +import { ApiError } from './app.interfaces'; @Injectable() export class AuthInterceptor implements HttpInterceptor { @@ -16,7 +16,8 @@ export class AuthInterceptor implements HttpInterceptor { private router: Router ) {} - // TODO separation of concerns: split into two interceptors, one for error handling, one for auth token addition + // TODO separation of concerns: split into two interceptors, + // one for error handling, one for auth token addition intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (this.mds.isApiValid) { let tokenStr = ''; @@ -34,7 +35,7 @@ export class AuthInterceptor implements HttpInterceptor { }); return next.handle(requestA).pipe( - catchError(e => { + catchError((e) => { const apiError = new ApiError(999); if (e instanceof HttpErrorResponse) { // TODO is the opposite case even possible? const httpError = e as HttpErrorResponse; diff --git a/src/app/app.interfaces.ts b/src/app/app.interfaces.ts index b5a2dc82..9c64cfc1 100644 --- a/src/app/app.interfaces.ts +++ b/src/app/app.interfaces.ts @@ -55,7 +55,9 @@ export interface AppError { export class ApiError { code: number; + info: string; + constructor(code: number, info = '') { this.code = code; this.info = info; diff --git a/src/app/app.module.spec.ts b/src/app/app.module.spec.ts index 30c3268c..a00ba3c9 100644 --- a/src/app/app.module.spec.ts +++ b/src/app/app.module.spec.ts @@ -1,4 +1,4 @@ -import {AppModule} from "./app.module"; +import { AppModule } from './app.module'; describe('AppModule', () => { let appModule: AppModule; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0151cf45..7ded18c4 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,32 +1,32 @@ import { BrowserModule } from '@angular/platform-browser'; -import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import {ApplicationModule, NgModule} from '@angular/core'; +import { ApplicationModule, NgModule } from '@angular/core'; +import { LocationStrategy, HashLocationStrategy } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatBadgeModule } from '@angular/material/badge'; +import { RouterModule } from '@angular/router'; import { ReactiveFormsModule } from '@angular/forms'; +import { FlexLayoutModule } from '@angular/flex-layout'; + +import { IqbComponentsModule } from 'iqb-components'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; - import { BackendService } from './backend.service'; -import { LocationStrategy, HashLocationStrategy } from '@angular/common'; -import { FlexLayoutModule } from '@angular/flex-layout'; -import {AuthInterceptor} from './app.interceptor'; -import { IqbComponentsModule } from 'iqb-components'; -import {MatButtonModule} from '@angular/material/button'; -import {MatCardModule} from '@angular/material/card'; -import {MatCheckboxModule} from '@angular/material/checkbox'; -import {MatDialog, MatDialogModule} from '@angular/material/dialog'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatIconModule} from '@angular/material/icon'; -import {MatInputModule} from '@angular/material/input'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatProgressBarModule} from '@angular/material/progress-bar'; -import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; -import {MatRadioModule} from '@angular/material/radio'; -import {MatTabsModule} from '@angular/material/tabs'; -import {MatToolbarModule} from '@angular/material/toolbar'; -import {MatTooltipModule} from '@angular/material/tooltip'; -import {MatBadgeModule} from '@angular/material/badge'; -import {RouterModule} from '@angular/router'; +import { AuthInterceptor } from './app.interceptor'; import { AppRootComponent } from './app-root/app-root.component'; import { SysCheckStarterComponent } from './app-root/sys-check-starter/sys-check-starter.component'; import { LoginComponent } from './app-root/login/login.component'; @@ -38,9 +38,6 @@ import { TestStarterComponent } from './app-root/test-starter/test-starter.compo import { MonitorStarterComponent } from './app-root/monitor-starter/monitor-starter.component'; import { PrivacyComponent } from './app-root/privacy/privacy.component'; - - - @NgModule({ declarations: [ AppComponent, diff --git a/src/app/backend.service.spec.ts b/src/app/backend.service.spec.ts index ffdc1397..e4f50fbf 100644 --- a/src/app/backend.service.spec.ts +++ b/src/app/backend.service.spec.ts @@ -1,8 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; import { BackendService } from './backend.service'; -import {HttpClientModule} from "@angular/common/http"; - describe('HttpClient testing', () => { beforeEach(() => { diff --git a/src/app/backend.service.ts b/src/app/backend.service.ts index 81115199..35a854ac 100644 --- a/src/app/backend.service.ts +++ b/src/app/backend.service.ts @@ -1,59 +1,53 @@ import { Injectable, Inject } from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {Observable, of} from 'rxjs'; -import {catchError, map, switchMap} from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; import { - SysCheckInfo, - AuthData, - WorkspaceData, - BookletData, ApiError, AccessObject + SysCheckInfo, + AuthData, + WorkspaceData, + BookletData, ApiError, AccessObject } from './app.interfaces'; -import {SysConfig} from './config/app.config'; - +import { SysConfig } from './config/app.config'; @Injectable({ providedIn: 'root' }) export class BackendService { - constructor( @Inject('SERVER_URL') private readonly serverUrl: string, private http: HttpClient ) {} - login(name: string, password: string): Observable<AuthData | number> { if (password) { return this.http - .put<AuthData>(this.serverUrl + 'session/admin', {name, password}) + .put<AuthData>(`${this.serverUrl}session/admin`, { name, password }) .pipe( catchError((err: ApiError) => { console.warn(`login Api-Error: ${err.code} ${err.info} `); return of(err.code); }), - switchMap(authData => { + switchMap((authData) => { if (typeof authData === 'number') { const errCode = authData as number; if (errCode === 400) { return this.http - .put<AuthData>(this.serverUrl + 'session/login', {name, password}) + .put<AuthData>(`${this.serverUrl}session/login`, { name, password }) .pipe(catchError((err: ApiError) => of(err.code))); - } else { - return of(errCode); } - } else { - return of(authData); + return of(errCode); } + return of(authData); }) ); - } else { - return this.nameOnlyLogin(name); } + return this.nameOnlyLogin(name); } nameOnlyLogin(name: string): Observable<AuthData | number> { return this.http - .put<AuthData>(this.serverUrl + 'session/login', {name}) + .put<AuthData>(`${this.serverUrl}session/login`, { name }) .pipe( catchError((err: ApiError) => { console.warn(`nameOnlyLogin Api-Error: ${err.code} ${err.info} `); @@ -64,7 +58,7 @@ export class BackendService { codeLogin(code: string): Observable<AuthData | number> { return this.http - .put<AuthData>(this.serverUrl + 'session/person', {code}) + .put<AuthData>(`${this.serverUrl}session/person`, { code }) .pipe( catchError((err: ApiError) => { console.warn(`codeLogin Api-Error: ${err.code} ${err.info} `); @@ -75,9 +69,9 @@ export class BackendService { getWorkspaceData(workspaceId: string): Observable<WorkspaceData> { return this.http - .get<WorkspaceData>(this.serverUrl + 'workspace/' + workspaceId) + .get<WorkspaceData>(`${this.serverUrl}workspace/${workspaceId}`) .pipe(catchError(() => { - console.warn('get workspace data failed for ' + workspaceId); + console.warn(`get workspace data failed for ${workspaceId}`); return of(<WorkspaceData>{ id: workspaceId, name: workspaceId, @@ -87,28 +81,28 @@ export class BackendService { } getGroupData(groupName: string): Observable<AccessObject> { - - // TODO find consistent terminology. in XSD they are called name & label and likewise (mostly) in newer BE-versions + // TODO find consistent terminology. in XSD they are called name & label + // and likewise (mostly) in newer BE-versions interface NameAndLabel { - name: string; - label: string; + name: string; + label: string; } return this.http - .get<NameAndLabel>(this.serverUrl + 'monitor/group/' + groupName) - .pipe(map((r: NameAndLabel): AccessObject => ({id: r.name, name: r.label}))) - .pipe(catchError(() => { - console.warn('get group data failed for ' + groupName); - return of(<AccessObject>{ - id: groupName, - name: groupName, - }); - })); - } + .get<NameAndLabel>(`${this.serverUrl}monitor/group/${groupName}`) + .pipe(map((r: NameAndLabel): AccessObject => ({ id: r.name, name: r.label }))) + .pipe(catchError(() => { + console.warn(`get group data failed for ${groupName}`); + return of(<AccessObject>{ + id: groupName, + name: groupName + }); + })); + } getSessionData(): Observable<AuthData | number> { return this.http - .get<AuthData>(this.serverUrl + 'session') + .get<AuthData>(`${this.serverUrl}session`) .pipe( catchError((err: ApiError) => of(err.code)) ); @@ -135,7 +129,7 @@ export class BackendService { startTest(bookletName: string): Observable<string | number> { return this.http - .put<number>(this.serverUrl + 'test', {bookletName}) + .put<number>(`${this.serverUrl}test`, { bookletName }) .pipe( map((testId: number) => String(testId)), catchError((err: ApiError) => of(err.code)) @@ -144,13 +138,13 @@ export class BackendService { getSysConfig(): Observable<SysConfig> { return this.http - .get<SysConfig>(this.serverUrl + `system/config`) + .get<SysConfig>(`${this.serverUrl}system/config`) .pipe(catchError(() => of(null))); } getSysCheckInfo(): Observable<SysCheckInfo[]> { return this.http - .get<SysCheckInfo[]>(this.serverUrl + 'sys-checks') + .get<SysCheckInfo[]>(`${this.serverUrl}sys-checks`) .pipe( catchError(() => { return of([]); diff --git a/src/app/config/app.config.ts b/src/app/config/app.config.ts index 02d34ab8..3c747628 100644 --- a/src/app/config/app.config.ts +++ b/src/app/config/app.config.ts @@ -1,7 +1,6 @@ -// @ts-ignoreinterface +import { CustomtextService } from 'iqb-components'; import customTextsDefault from './custom-texts.json'; -import {CustomtextService} from "iqb-components"; -import {KeyValuePairs} from "../app.interfaces"; +import { KeyValuePairs } from '../app.interfaces'; export interface SysConfig { customTexts: KeyValuePairs; @@ -10,14 +9,14 @@ export interface SysConfig { testConfig: KeyValuePairs; } -export class AppConfig { +export class AppConfig { constructor( private cts: CustomtextService ) { } setDefaultCustomTexts() { - let ctDefaults = {}; + const ctDefaults = {}; for (const k of Object.keys(customTextsDefault)) { ctDefaults[k] = customTextsDefault[k].defaultvalue } diff --git a/src/app/group-monitor/backend.service.ts b/src/app/group-monitor/backend.service.ts index 3b1b7998..8d240b76 100644 --- a/src/app/group-monitor/backend.service.ts +++ b/src/app/group-monitor/backend.service.ts @@ -1,77 +1,78 @@ -import {Injectable} from '@angular/core'; -import {Observable, of, Subscription} from 'rxjs'; -import {catchError} from 'rxjs/operators'; -import {BookletError, GroupData, TestSession} from './group-monitor.interfaces'; -import {WebsocketBackendService} from '../shared/websocket-backend.service'; -import {HttpHeaders} from '@angular/common/http'; -import {ApiError} from '../app.interfaces'; +import { Injectable } from '@angular/core'; +import { HttpHeaders } from '@angular/common/http'; +import { Observable, of, Subscription } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +import { BookletError, GroupData, TestSession } from './group-monitor.interfaces'; +import { WebsocketBackendService } from '../shared/websocket-backend.service'; +import { ApiError } from '../app.interfaces'; @Injectable() export class BackendService extends WebsocketBackendService<TestSession[]> { - public pollingEndpoint = '/monitor/test-sessions'; - public pollingInterval = 5000; - public wsChannelName = 'test-sessions'; - public initialData: TestSession[] = []; + public pollingEndpoint = '/monitor/test-sessions'; + public pollingInterval = 5000; + public wsChannelName = 'test-sessions'; + public initialData: TestSession[] = []; - public observeSessionsMonitor(): Observable<TestSession[]> { - return this.observeEndpointAndChannel(); - } + public observeSessionsMonitor(): Observable<TestSession[]> { + return this.observeEndpointAndChannel(); + } - public getBooklet(bookletName: string): Observable<string|BookletError> { - console.log('load booklet for ' + bookletName); + public getBooklet(bookletName: string): Observable<string|BookletError> { + console.log('load booklet for ' + bookletName); - const headers = new HttpHeaders({ 'Content-Type': 'text/xml' }).set('Accept', 'text/xml'); + const headers = new HttpHeaders({ 'Content-Type': 'text/xml' }).set('Accept', 'text/xml'); - const missingFileError: BookletError = {error: 'missing-file'}; - const generalError: BookletError = {error: 'general'}; + const missingFileError: BookletError = {error: 'missing-file'}; + const generalError: BookletError = {error: 'general'}; - return this.http - .get(this.serverUrl + `booklet/${bookletName}`, {headers, responseType: 'text'}) - .pipe( - catchError((err: ApiError) => { - console.warn(`getTestData Api-Error: ${err.code} ${err.info}`); - if (err.code === 404) { - // could potentially happen when booklet file was removed since test was started - // TODO interceptor be omitted - return of(missingFileError); - } else { - // TODO should interceptor should have interfered and moved to error-page ... - // https://github.com/iqb-berlin/testcenter-frontend/issues/53 - return of(generalError); - } - }) - ); - } + return this.http + .get(this.serverUrl + `booklet/${bookletName}`, {headers, responseType: 'text'}) + .pipe( + catchError((err: ApiError) => { + console.warn(`getTestData Api-Error: ${err.code} ${err.info}`); + if (err.code === 404) { + // could potentially happen when booklet file was removed since test was started + // TODO interceptor be omitted + return of(missingFileError); + } else { + // TODO should interceptor should have interfered and moved to error-page ... + // https://github.com/iqb-berlin/testcenter-frontend/issues/53 + return of(generalError); + } + }) + ); + } - public getGroupData(groupName: string): Observable<GroupData> { - return this.http - .get<GroupData>(this.serverUrl + `monitor/group/${groupName}`) - .pipe(catchError(() => { - // TODO interceptor should have interfered and moved to error-page ... - // https://github.com/iqb-berlin/testcenter-frontend/issues/53 - console.warn(`failed: monitor/group/${groupName}`); - return of(<GroupData>{ - name: 'error', - label: 'error', - }); - })); - } + public getGroupData(groupName: string): Observable<GroupData> { + return this.http + .get<GroupData>(this.serverUrl + `monitor/group/${groupName}`) + .pipe(catchError(() => { + // TODO interceptor should have interfered and moved to error-page ... + // https://github.com/iqb-berlin/testcenter-frontend/issues/53 + console.warn(`failed: monitor/group/${groupName}`); + return of(<GroupData>{ + name: 'error', + label: 'error' + }); + })); + } - public command(keyword: string, args: string[], testIds: number[]): Subscription { - console.log('SEND COMMAND: ' + keyword + ' ' + args.join(' ') + ' to ' + testIds.join(', ')); - return this.http - .put( - this.serverUrl + `monitor/command`, - {keyword, arguments: args, timestamp: Date.now() / 1000, testIds} - ) - .pipe( - catchError(() => { - // TODO interceptor should have interfered and moved to error-page ... - // https://github.com/iqb-berlin/testcenter-frontend/issues/53 - console.warn(`failed: command`, keyword, args, testIds); - return of(false); - }) - ) - .subscribe(); - } + public command(keyword: string, args: string[], testIds: number[]): Subscription { + console.log('SEND COMMAND: ' + keyword + ' ' + args.join(' ') + ' to ' + testIds.join(', ')); + return this.http + .put( + this.serverUrl + `monitor/command`, + { keyword, arguments: args, timestamp: Date.now() / 1000, testIds } + ) + .pipe( + catchError(() => { + // TODO interceptor should have interfered and moved to error-page ... + // https://github.com/iqb-berlin/testcenter-frontend/issues/53 + console.warn(`failed: command`, keyword, args, testIds); + return of(false); + }) + ) + .subscribe(); + } } diff --git a/src/app/group-monitor/booklet.service.spec.ts b/src/app/group-monitor/booklet.service.spec.ts index 77a095dc..359ad441 100644 --- a/src/app/group-monitor/booklet.service.spec.ts +++ b/src/app/group-monitor/booklet.service.spec.ts @@ -1,98 +1,86 @@ import { TestBed } from '@angular/core/testing'; +import { Observable, of } from 'rxjs'; import { BookletService } from './booklet.service'; -import {BackendService} from './backend.service'; -import {Observable, of} from 'rxjs'; +import { BackendService } from './backend.service'; class MockBackendService { - public getBooklet(bookletName: string): Observable<string> { - return of('<booklet>TODO insert nice booklet</booklet>'); - } + public getBooklet(bookletName: string): Observable<string> { + return of('<booklet>TODO insert nice booklet</booklet>'); + } } - describe('BookletService', () => { - let service: BookletService; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - BookletService, - { - provide: BackendService, - useValue: new MockBackendService() - } - ] - }); - service = TestBed.inject(BookletService); + let service: BookletService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + BookletService, + { + provide: BackendService, + useValue: new MockBackendService() + } + ] }); + service = TestBed.inject(BookletService); + }); + it('should be created', () => { + expect(service).toBeTruthy(); + }); - it('should be created', () => { - expect(service).toBeTruthy(); - }); + xit('parseBookletXml() should parse a Booklet-Xml to a Booklet-Object', () => { + // TODO implement unit.test + }); + xit('parseMetadata() should parse the <metadata>-element', () => { + // TODO implement unit.test + }); - xit('parseBookletXml() should parse a Booklet-Xml to a Booklet-Object', () => { - // TODO implement unit.test - }); + xit('parseTestlet() should parse the <testlet>-element', () => { + // TODO implement unit.test + }); + xit('parseUnitOrTestlet() should parse the <unit>-element or call parseTestlet()', () => { + // TODO implement unit.test + }); - xit('parseMetadata() should parse the <metadata>-element', () => { - // TODO implement unit.test - }); + xit('parseRestrictions() should parse the <restrictions>-element', () => { + // TODO implement unit.test + }); - xit('parseTestlet() should parse the <testlet>-element', () => { - // TODO implement unit.test + describe('XML functions', () => { + xit('xmlGetChildTextIfExists() should return a child element\'s text of a given name from a domElement if that exists', () => { + // TODO implement unit.test }); - - xit('parseUnitOrTestlet() should parse the <unit>-element or call parseTestlet()', () => { - // TODO implement unit.test + xit('xmlGetChildIfExists() should return a child element of a given name from a domElement if that exists', () => { + // TODO implement unit.test }); - - xit('parseRestrictions() should parse the <restrictions>-element', () => { - // TODO implement unit.test + xit('xmlGetDirectChildrenByTagName() should return all children from a domElement with a given name', () => { + // TODO implement unit.test }); + it('xmlCountChildrenOfTagNames() should count all (grand-)children of the defined types', () => { + const domParser = new DOMParser(); + const testXml = '<root><a>x<b>x</b><b /><b></b></a><b><!-- ! --><c>x</c>x</b><a><b></b></a>x</root>'; + const testContent = domParser.parseFromString(testXml, 'text/xml').documentElement; - describe('XML functions', () => { - - - xit('xmlGetChildTextIfExists() should return a child element\'s text of a given name from a domElement if that exists', () => { - // TODO implement unit.test - }); - - - xit('xmlGetChildIfExists() should return a child element of a given name from a domElement if that exists', () => { - // TODO implement unit.test - }); - - - xit('xmlGetDirectChildrenByTagName() should return all children from a domElement with a given name', () => { - // TODO implement unit.test - }); - - - it('xmlCountChildrenOfTagNames() should count all (grand-)children of the defined types', () => { - const domParser = new DOMParser(); - const testXml = '<root><a>x<b>x</b><b /><b></b></a><b><!-- ! --><c>x</c>x</b><a><b></b></a>x</root>'; - const testContent = domParser.parseFromString(testXml, 'text/xml').documentElement; - - let result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['a']); - expect(result).withContext('a').toEqual(2); + let result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['a']); + expect(result).withContext('a').toEqual(2); - result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['b']); - expect(result).withContext('b').toEqual(5); + result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['b']); + expect(result).withContext('b').toEqual(5); - result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['c']); - expect(result).withContext('c').toEqual(1); + result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['c']); + expect(result).withContext('c').toEqual(1); - result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['d']); - expect(result).withContext('c').toEqual(0); + result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['d']); + expect(result).withContext('c').toEqual(0); - result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['a', 'b', 'c', 'd']); - expect(result).withContext('c').toEqual(8); - }); + result = BookletService['xmlCountChildrenOfTagNames'](testContent, ['a', 'b', 'c', 'd']); + expect(result).withContext('c').toEqual(8); }); + }); }); diff --git a/src/app/group-monitor/booklet.service.ts b/src/app/group-monitor/booklet.service.ts index da4499d1..f98ace29 100644 --- a/src/app/group-monitor/booklet.service.ts +++ b/src/app/group-monitor/booklet.service.ts @@ -1,152 +1,147 @@ -import {Injectable} from '@angular/core'; -import {MainDataService} from '../maindata.service'; -import {BackendService} from './backend.service'; -import {Observable, of} from 'rxjs'; -import {map, shareReplay} from 'rxjs/operators'; -import {Booklet, BookletError, BookletMetadata, Restrictions, Testlet, Unit} from './group-monitor.interfaces'; -import {BookletConfig} from '../config/booklet-config'; - +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { map, shareReplay } from 'rxjs/operators'; +import { MainDataService } from '../maindata.service'; +import { BackendService } from './backend.service'; +import { + Booklet, BookletError, BookletMetadata, Restrictions, Testlet, Unit +} from './group-monitor.interfaces'; +import { BookletConfig } from '../config/booklet-config'; @Injectable() export class BookletService { - - public booklets: Observable<Booklet|BookletError>[] = []; - - constructor( - private bs: BackendService - ) { } - - private static parseBookletXml(xmlString: string): Booklet|BookletError { - try { - const domParser = new DOMParser(); - const bookletElement = domParser.parseFromString(xmlString, 'text/xml').documentElement; - - if (bookletElement.nodeName !== 'Booklet') { - console.warn('XML-root is not `Booklet`'); - return {error: 'xml'}; - } - - return { - units: BookletService.parseTestlet(BookletService.xmlGetChildIfExists(bookletElement, 'Units')), - metadata: BookletService.parseMetadata(bookletElement), - config: BookletService.parseBookletConfig(bookletElement) - }; - } catch (error) { - console.warn('Error reading booklet XML:', error); - return {error: 'xml'}; - } + public booklets: Observable<Booklet|BookletError>[] = []; + + constructor( + private bs: BackendService + ) { } + + private static parseBookletXml(xmlString: string): Booklet|BookletError { + try { + const domParser = new DOMParser(); + const bookletElement = domParser.parseFromString(xmlString, 'text/xml').documentElement; + + if (bookletElement.nodeName !== 'Booklet') { + console.warn('XML-root is not `Booklet`'); + return { error: 'xml' }; + } + + return { + units: BookletService.parseTestlet(BookletService.xmlGetChildIfExists(bookletElement, 'Units')), + metadata: BookletService.parseMetadata(bookletElement), + config: BookletService.parseBookletConfig(bookletElement) + }; + } catch (error) { + console.warn('Error reading booklet XML:', error); + return { error: 'xml' }; } - - private static parseBookletConfig(bookletElement: Element): BookletConfig { - const bookletConfigElements = BookletService.xmlGetChildIfExists(bookletElement, 'BookletConfig', true); - const bookletConfig = new BookletConfig(); - bookletConfig.setFromKeyValuePairs(MainDataService.getTestConfig()); - if (bookletConfigElements) { - bookletConfig.setFromXml(bookletConfigElements[0]); - } - return bookletConfig; - } - - private static parseMetadata(bookletElement: Element): BookletMetadata { - const metadataElement = BookletService.xmlGetChildIfExists(bookletElement, 'Metadata'); - return { - id: BookletService.xmlGetChildTextIfExists(metadataElement, 'Id'), - label: BookletService.xmlGetChildTextIfExists(metadataElement, 'Label'), - description: BookletService.xmlGetChildTextIfExists(metadataElement, 'Description', true), - }; - } - - private static parseTestlet(testletElement: Element): Testlet { - // TODO id will be mandatory (https://github.com/iqb-berlin/testcenter-iqb-php/issues/116), the remove fallback to '' - return { - id: testletElement.getAttribute('id') || '', - label: testletElement.getAttribute('label') || '', - restrictions: BookletService.parseRestrictions(testletElement), - children: BookletService.xmlGetDirectChildrenByTagName(testletElement, ['Unit', 'Testlet']) - .map(BookletService.parseUnitOrTestlet), - descendantCount: BookletService.xmlCountChildrenOfTagNames(testletElement, ['Unit']) - }; + } + + private static parseBookletConfig(bookletElement: Element): BookletConfig { + const bookletConfigElements = BookletService.xmlGetChildIfExists(bookletElement, 'BookletConfig', true); + const bookletConfig = new BookletConfig(); + bookletConfig.setFromKeyValuePairs(MainDataService.getTestConfig()); + if (bookletConfigElements) { + bookletConfig.setFromXml(bookletConfigElements[0]); } - - private static parseUnitOrTestlet(unitOrTestletElement: Element): (Unit|Testlet) { - if (unitOrTestletElement.tagName === 'Unit') { - return { - id: unitOrTestletElement.getAttribute('alias') || unitOrTestletElement.getAttribute('id'), - label: unitOrTestletElement.getAttribute('label'), - labelShort: unitOrTestletElement.getAttribute('labelshort') - }; - } - return BookletService.parseTestlet(unitOrTestletElement); + return bookletConfig; + } + + private static parseMetadata(bookletElement: Element): BookletMetadata { + const metadataElement = BookletService.xmlGetChildIfExists(bookletElement, 'Metadata'); + return { + id: BookletService.xmlGetChildTextIfExists(metadataElement, 'Id'), + label: BookletService.xmlGetChildTextIfExists(metadataElement, 'Label'), + description: BookletService.xmlGetChildTextIfExists(metadataElement, 'Description', true) + }; + } + +private static parseTestlet(testletElement: Element): Testlet { + // TODO id will be mandatory (https://github.com/iqb-berlin/testcenter-iqb-php/issues/116), the remove fallback to '' + return { + id: testletElement.getAttribute('id') || '', + label: testletElement.getAttribute('label') || '', + restrictions: BookletService.parseRestrictions(testletElement), + children: BookletService.xmlGetDirectChildrenByTagName(testletElement, ['Unit', 'Testlet']) + .map(BookletService.parseUnitOrTestlet), + descendantCount: BookletService.xmlCountChildrenOfTagNames(testletElement, ['Unit']) + }; + } + + private static parseUnitOrTestlet(unitOrTestletElement: Element): (Unit|Testlet) { + if (unitOrTestletElement.tagName === 'Unit') { + return { + id: unitOrTestletElement.getAttribute('alias') || unitOrTestletElement.getAttribute('id'), + label: unitOrTestletElement.getAttribute('label'), + labelShort: unitOrTestletElement.getAttribute('labelshort') + }; } - - private static parseRestrictions(testletElement: Element): Restrictions { - const restrictions: Restrictions = {}; - const restrictionsElement = BookletService.xmlGetChildIfExists(testletElement, 'Restrictions', true); - if (!restrictionsElement) { - return restrictions; - } - const codeToEnterElement = restrictionsElement.querySelector('CodeToEnter'); - if (codeToEnterElement) { - restrictions.codeToEnter = { - code: codeToEnterElement.getAttribute('code'), - message: codeToEnterElement.textContent - }; - } - const timeMaxElement = restrictionsElement.querySelector('TimeMax'); - if (timeMaxElement) { - restrictions.timeMax = { - minutes: parseFloat(timeMaxElement.getAttribute('minutes')), - }; - } - return restrictions; + return BookletService.parseTestlet(unitOrTestletElement); + } + + private static parseRestrictions(testletElement: Element): Restrictions { + const restrictions: Restrictions = {}; + const restrictionsElement = BookletService.xmlGetChildIfExists(testletElement, 'Restrictions', true); + if (!restrictionsElement) { + return restrictions; } - - private static xmlGetChildIfExists(element: Element, childName: string, isOptional: boolean = false): Element { - const elements = BookletService.xmlGetDirectChildrenByTagName(element, [childName]); - if (!elements.length && !isOptional) { - throw new Error(`Missing field: '${childName}'`); - } - return elements.length ? elements[0] : null; + const codeToEnterElement = restrictionsElement.querySelector('CodeToEnter'); + if (codeToEnterElement) { + restrictions.codeToEnter = { + code: codeToEnterElement.getAttribute('code'), + message: codeToEnterElement.textContent + }; } - - private static xmlGetChildTextIfExists(element: Element, childName: string, isOptional: boolean = false): string { - const childElement = BookletService.xmlGetChildIfExists(element, childName, isOptional); - return childElement ? childElement.textContent : ''; + const timeMaxElement = restrictionsElement.querySelector('TimeMax'); + if (timeMaxElement) { + restrictions.timeMax = { + minutes: parseFloat(timeMaxElement.getAttribute('minutes')) + }; } + return restrictions; + } - private static xmlGetDirectChildrenByTagName(element: Element, tagNames: string[]): Element[] { - return [].slice.call(element.childNodes) - .filter((elem: Element) => (elem.nodeType === 1)) - .filter((elem: Element) => (tagNames.indexOf(elem.tagName) > -1)); + private static xmlGetChildIfExists(element: Element, childName: string, isOptional: boolean = false): Element { + const elements = BookletService.xmlGetDirectChildrenByTagName(element, [childName]); + if (!elements.length && !isOptional) { + throw new Error(`Missing field: '${childName}'`); } - - private static xmlCountChildrenOfTagNames(element: Element, tagNames: string[]): number { - return element.querySelectorAll(tagNames.join(', ')).length; + return elements.length ? elements[0] : null; + } + + private static xmlGetChildTextIfExists(element: Element, childName: string, isOptional: boolean = false): string { + const childElement = BookletService.xmlGetChildIfExists(element, childName, isOptional); + return childElement ? childElement.textContent : ''; + } + + private static xmlGetDirectChildrenByTagName(element: Element, tagNames: string[]): Element[] { + return [].slice.call(element.childNodes) + .filter((elem: Element) => (elem.nodeType === 1)) + .filter((elem: Element) => (tagNames.indexOf(elem.tagName) > -1)); + } + + private static xmlCountChildrenOfTagNames(element: Element, tagNames: string[]): number { + return element.querySelectorAll(tagNames.join(', ')).length; + } + + public getBooklet(bookletName: string): Observable<Booklet|BookletError> { + if (typeof this.booklets[bookletName] !== 'undefined') { + // console.log('FORWARDING booklet for ' + bookletName + ''); + return this.booklets[bookletName]; } - - public getBooklet(bookletName: string): Observable<Booklet|BookletError> { - if (typeof this.booklets[bookletName] !== 'undefined') { - // console.log('FORWARDING booklet for ' + bookletName + ''); - return this.booklets[bookletName]; - } - if (bookletName === '') { - // console.log("EMPTY bookletID"); - this.booklets[bookletName] = of<Booklet|BookletError>({error: 'missing-id'}); - - } else { - // console.log('LOADING testletOrUnit data for ' + bookletName + ' not available. loading'); - this.booklets[bookletName] = this.bs.getBooklet(bookletName) - .pipe( - map((response: string|BookletError) => { - return (typeof response === 'string') ? BookletService.parseBookletXml(response) : response; - }), - shareReplay(1) - ); - } - return this.booklets[bookletName]; + if (bookletName === '') { + // console.log("EMPTY bookletID"); + this.booklets[bookletName] = of<Booklet|BookletError>({ error: 'missing-id' }); + } else { + // console.log('LOADING testletOrUnit data for ' + bookletName + ' not available. loading'); + this.booklets[bookletName] = this.bs.getBooklet(bookletName) + .pipe( + map((response: string|BookletError) => { + return (typeof response === 'string') ? BookletService.parseBookletXml(response) : response; + }), + shareReplay(1) + ); } + return this.booklets[bookletName]; + } } - - - - diff --git a/src/app/group-monitor/group-monitor-routing.module.ts b/src/app/group-monitor/group-monitor-routing.module.ts index 491e4c7a..084181af 100644 --- a/src/app/group-monitor/group-monitor-routing.module.ts +++ b/src/app/group-monitor/group-monitor-routing.module.ts @@ -1,10 +1,9 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import {GroupMonitorComponent} from './group-monitor.component'; - +import { GroupMonitorComponent } from './group-monitor.component'; const routes: Routes = [ - {path: ':group-name', component: GroupMonitorComponent} + { path: ':group-name', component: GroupMonitorComponent } ]; @NgModule({ diff --git a/src/app/group-monitor/group-monitor.component.ts b/src/app/group-monitor/group-monitor.component.ts index 3ffcd245..e57685a0 100644 --- a/src/app/group-monitor/group-monitor.component.ts +++ b/src/app/group-monitor/group-monitor.component.ts @@ -1,19 +1,23 @@ -import {Component, HostListener, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {BackendService} from './backend.service'; -import {BehaviorSubject, combineLatest, Observable, Subject, Subscription} from 'rxjs'; +import { + Component, HostListener, OnDestroy, OnInit, ViewChild +} from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Sort } from '@angular/material/sort'; +import { MatSidenav } from '@angular/material/sidenav'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { + BehaviorSubject, combineLatest, Observable, Subject, Subscription +} from 'rxjs'; +import { map, tap } from 'rxjs/operators'; + +import { BackendService } from './backend.service'; import { GroupData, TestSession, TestViewDisplayOptions, - TestViewDisplayOptionKey, Testlet, Unit, isUnit, Selected, TestSessionFilter, + TestViewDisplayOptionKey, Testlet, Unit, isUnit, Selected, TestSessionFilter } from './group-monitor.interfaces'; -import {ActivatedRoute} from '@angular/router'; -import {ConnectionStatus} from '../shared/websocket-backend.service'; -import {map, tap} from 'rxjs/operators'; -import {Sort} from '@angular/material/sort'; -import {MatSidenav} from '@angular/material/sidenav'; -import {MatCheckboxChange} from '@angular/material/checkbox'; - +import { ConnectionStatus } from '../shared/websocket-backend.service'; @Component({ selector: 'app-group-monitor', @@ -21,10 +25,9 @@ import {MatCheckboxChange} from '@angular/material/checkbox'; styleUrls: ['./group-monitor.component.css'] }) export class GroupMonitorComponent implements OnInit, OnDestroy { - constructor( - private route: ActivatedRoute, - private bs: BackendService, + private route: ActivatedRoute, + private bs: BackendService ) {} ownGroup$: Observable<GroupData>; @@ -41,6 +44,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { selectionMode: 'block', selectionSpreading: 'booklet' }; + filterOptions: {label: string, filter: TestSessionFilter, selected: boolean}[] = [ { label: 'gesperrte', @@ -60,7 +64,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { subValue: 'RUNNING', not: true } - }, + } ]; selectedElement: Selected = { @@ -68,6 +72,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { element: undefined, spreading: false }; + markedElement: Testlet|Unit|null = null; checkedSessions: {[sessionTestSessionId: number]: TestSession} = {}; allSessionsChecked = false; @@ -119,7 +124,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { this.connectionStatus$ = this.bs.connectionStatus$; } - ngOnDestroy() { + ngOnDestroy(): void { if (this.routingSubscription !== null) { this.routingSubscription.unsubscribe(); } @@ -146,7 +151,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { } private filterSessions(sessions: TestSession[], filters: TestSessionFilter[]): TestSession[] { - return sessions.filter(session => this.applyFilters(session, filters)); + return sessions.filter((session) => this.applyFilters(session, filters)); } private applyFilters(session: TestSession, filters: TestSessionFilter[]): boolean { @@ -175,12 +180,12 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { private updateChecked(sessions: TestSession[]): void { const newCheckedSessions: {[sessionFullId: number]: TestSession} = {}; sessions - .forEach(session => { - const sessionFullId = GroupMonitorComponent.getPersonXTestId(session); - if (typeof this.checkedSessions[sessionFullId] !== 'undefined') { - newCheckedSessions[sessionFullId] = session; - } - }); + .forEach((session) => { + const sessionFullId = GroupMonitorComponent.getPersonXTestId(session); + if (typeof this.checkedSessions[sessionFullId] !== 'undefined') { + newCheckedSessions[sessionFullId] = session; + } + }); this.checkedSessions = newCheckedSessions; } @@ -248,27 +253,27 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { testCommandResume() { const testIds = Object.values(this.checkedSessions) - .filter(session => session.testId && session.testId > -1) - .filter(session => GroupMonitorComponent.hasState(session.testState, 'status', 'running')) - .filter(session => GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) - .map(session => session.testId); + .filter((session) => session.testId && session.testId > -1) + .filter((session) => GroupMonitorComponent.hasState(session.testState, 'status', 'running')) + .filter((session) => GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) + .map((session) => session.testId); this.bs.command('resume', [], testIds); } testCommandPause() { const testIds = Object.values(this.checkedSessions) - .filter(session => session.testId && session.testId > -1) - .filter(session => GroupMonitorComponent.hasState(session.testState, 'status', 'running')) - .filter(session => !GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) - .map(session => session.testId); + .filter((session) => session.testId && session.testId > -1) + .filter((session) => GroupMonitorComponent.hasState(session.testState, 'status', 'running')) + .filter((session) => !GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) + .map((session) => session.testId); this.bs.command('pause', [], testIds); } testCommandGoto() { if ((this.sessionCheckedGroupCount === 1) && (Object.keys(this.checkedSessions).length > 0)) { const testIds = Object.values(this.checkedSessions) - .filter(session => session.testId && session.testId > -1) - .map(session => session.testId); + .filter((session) => session.testId && session.testId > -1) + .map((session) => session.testId); this.bs.command('goto', ['id', GroupMonitorComponent.getFirstUnit(this.selectedElement.element).id], testIds); } } @@ -318,8 +323,8 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { invertChecked(event: Event): boolean { event.preventDefault(); const unChecked = this.sessions$.getValue() - .filter(session => session.testId && session.testId > -1) - .filter(session => !this.isChecked(session)); + .filter((session) => session.testId && session.testId > -1) + .filter((session) => !this.isChecked(session)); this.replaceCheckedSessions(unChecked); return false; } @@ -345,16 +350,16 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { private replaceCheckedSessions(sessionsToCheck: TestSession[]) { const newCheckedSessions = {}; sessionsToCheck - .forEach(session => newCheckedSessions[GroupMonitorComponent.getPersonXTestId(session)] = session); + .forEach((session) => newCheckedSessions[GroupMonitorComponent.getPersonXTestId(session)] = session); this.checkedSessions = newCheckedSessions; this.onCheckedChanged(); } private onCheckedChanged() { this.sessionCheckedGroupCount = Object.values(this.checkedSessions) - .map(session => session.bookletName) - .filter((value, index, self) => self.indexOf(value) === index) - .length; + .map((session) => session.bookletName) + .filter((value, index, self) => self.indexOf(value) === index) + .length; const checkableSessions = this.sessions$.getValue().filter(session => session.testId && session.testId > -1); this.allSessionsChecked = (checkableSessions.length === this.countCheckedSessions()); if (this.sessionCheckedGroupCount > 1) { @@ -364,7 +369,7 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { isPauseAllowed(): boolean { const activeSessions = Object.values(this.checkedSessions).length && Object.values(this.checkedSessions) - .filter(session => GroupMonitorComponent.hasState(session.testState, 'status', 'running')); + .filter((session) => GroupMonitorComponent.hasState(session.testState, 'status', 'running')); return activeSessions.length && activeSessions .filter(session => GroupMonitorComponent.hasState(session.testState, 'status', 'running')) .filter(session => GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) @@ -373,9 +378,9 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { isResumeAllowed() { const activeSessions = Object.values(this.checkedSessions) - .filter(session => GroupMonitorComponent.hasState(session.testState, 'status', 'running')); + .filter((session) => GroupMonitorComponent.hasState(session.testState, 'status', 'running')); return activeSessions.length && activeSessions - .filter(session => !GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) - .length === 0; + .filter((session) => !GroupMonitorComponent.hasState(session.testState, 'CONTROLLER', 'PAUSED')) + .length === 0; } } diff --git a/src/app/group-monitor/group-monitor.interfaces.ts b/src/app/group-monitor/group-monitor.interfaces.ts index 39a47a93..9e393b44 100644 --- a/src/app/group-monitor/group-monitor.interfaces.ts +++ b/src/app/group-monitor/group-monitor.interfaces.ts @@ -1,109 +1,108 @@ -import {BookletConfig} from '../config/booklet-config'; +import { BookletConfig } from '../config/booklet-config'; export interface TestSession { - personId: number; - personLabel?: string; - groupName?: string; - groupLabel?: string; - mode?: string; - testId?: number; - bookletName?: string; - testState: { - [testStateKey: string]: string - }; - unitName?: string; - unitState: { - [unitStateKey: string]: string - }; - timestamp: number; + personId: number; + personLabel?: string; + groupName?: string; + groupLabel?: string; + mode?: string; + testId?: number; + bookletName?: string; + testState: { + [testStateKey: string]: string + }; + unitName?: string; + unitState: { + [unitStateKey: string]: string + }; + timestamp: number; } export interface Booklet { - metadata: BookletMetadata; - config: BookletConfig; - restrictions?: Restrictions; - units: Testlet; + metadata: BookletMetadata; + config: BookletConfig; + restrictions?: Restrictions; + units: Testlet; } export interface BookletError { - error: 'xml' | 'missing-id' | 'missing-file' | 'general'; + error: 'xml' | 'missing-id' | 'missing-file' | 'general'; } export interface BookletMetadata { - id: string; - label: string; - description: string; - owner?: string; - lastchange?: string; - status?: string; - project?: string; + id: string; + label: string; + description: string; + owner?: string; + lastchange?: string; + status?: string; + project?: string; } export interface Testlet { - id: string; - label: string; - restrictions?: Restrictions; - children: (Unit|Testlet)[]; - descendantCount: number; + id: string; + label: string; + restrictions?: Restrictions; + children: (Unit|Testlet)[]; + descendantCount: number; } export interface Unit { - id: string; - label: string; - labelShort: string; + id: string; + label: string; + labelShort: string; } export interface Restrictions { - codeToEnter?: { - code: string; - message: string; - }; - timeMax?: { - minutes: number - }; + codeToEnter?: { + code: string; + message: string; + }; + timeMax?: { + minutes: number + }; } export interface GroupData { - name: string; - label: string; + name: string; + label: string; } export type TestViewDisplayOptionKey = 'view' | 'groupColumn'; export interface TestSessionFilter { - type: 'groupName' | 'bookletName' | 'testState' | 'mode'; - value: string; - subValue?: string; - not?: true; + type: 'groupName' | 'bookletName' | 'testState' | 'mode'; + value: string; + subValue?: string; + not?: true; } export interface TestViewDisplayOptions { - view: 'full' | 'medium' | 'small'; - groupColumn: 'show' | 'hide'; - selectionMode: 'block' | 'unit'; - selectionSpreading: 'booklet' | 'all'; + view: 'full' | 'medium' | 'small'; + groupColumn: 'show' | 'hide'; + selectionMode: 'block' | 'unit'; + selectionSpreading: 'booklet' | 'all'; } export function isUnit(testletOrUnit: Testlet|Unit): testletOrUnit is Unit { - return !('children' in testletOrUnit); + return !('children' in testletOrUnit); } - export interface UnitContext { - unit?: Unit; - parent?: Testlet; - ancestor?: Testlet; - unitCount: number; - unitCountGlobal: number; - indexGlobal: number; - indexLocal: number; - indexAncestor: number; - unitCountAncestor: number; + unit?: Unit; + parent?: Testlet; + ancestor?: Testlet; + unitCount: number; + unitCountGlobal: number; + indexGlobal: number; + indexLocal: number; + indexAncestor: number; + unitCountAncestor: number; } export interface Selected { - element: Unit|Testlet|null; - session?: TestSession; - spreading: boolean; - inversion?: boolean; + element: Unit|Testlet|null; + session?: TestSession; + spreading: boolean; + inversion?: boolean; } diff --git a/src/app/group-monitor/group-monitor.module.ts b/src/app/group-monitor/group-monitor.module.ts index 7061350f..8e35f500 100644 --- a/src/app/group-monitor/group-monitor.module.ts +++ b/src/app/group-monitor/group-monitor.module.ts @@ -1,56 +1,53 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - -import { GroupMonitorRoutingModule } from './group-monitor-routing.module'; -import { GroupMonitorComponent } from './group-monitor.component'; - -import {MatTableModule} from '@angular/material/table'; -import {MatTooltipModule} from '@angular/material/tooltip'; +import { MatIconModule } from '@angular/material/icon'; +import { MatBadgeModule } from '@angular/material/badge'; +import { FlexModule } from '@angular/flex-layout'; +import { MatSortModule } from '@angular/material/sort'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatButtonModule } from '@angular/material/button'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { FormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { MatChipsModule } from '@angular/material/chips'; import { CdkTableModule } from '@angular/cdk/table'; -import {BackendService} from './backend.service'; -import {BookletService} from './booklet.service'; +import { GroupMonitorRoutingModule } from './group-monitor-routing.module'; +import { GroupMonitorComponent } from './group-monitor.component'; +import { BackendService } from './backend.service'; +import { BookletService } from './booklet.service'; import { TestViewComponent } from './test-view/test-view.component'; -import {MatIconModule} from '@angular/material/icon'; -import {MatBadgeModule} from '@angular/material/badge'; -import {FlexModule} from '@angular/flex-layout'; -import {MatSortModule} from '@angular/material/sort'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatButtonModule} from '@angular/material/button'; -import {MatRadioModule} from '@angular/material/radio'; -import {MatSidenavModule} from '@angular/material/sidenav'; -import {FormsModule} from '@angular/forms'; -import {MatCheckboxModule} from '@angular/material/checkbox'; - @NgModule({ - declarations: [ - GroupMonitorComponent, - TestViewComponent, - ], - imports: [ - CommonModule, - GroupMonitorRoutingModule, - MatTableModule, - MatTooltipModule, - CdkTableModule, - MatChipsModule, - MatIconModule, - MatBadgeModule, - FlexModule, - MatSortModule, - MatMenuModule, - MatButtonModule, - MatRadioModule, - FormsModule, - MatSidenavModule, - MatCheckboxModule - ], - providers: [ - BackendService, - BookletService - ], + declarations: [ + GroupMonitorComponent, + TestViewComponent + ], + imports: [ + CommonModule, + GroupMonitorRoutingModule, + MatTableModule, + MatTooltipModule, + CdkTableModule, + MatChipsModule, + MatIconModule, + MatBadgeModule, + FlexModule, + MatSortModule, + MatMenuModule, + MatButtonModule, + MatRadioModule, + FormsModule, + MatSidenavModule, + MatCheckboxModule + ], + providers: [ + BackendService, + BookletService + ] }) export class GroupMonitorModule { } diff --git a/src/app/group-monitor/test-view/test-view.component.spec.ts b/src/app/group-monitor/test-view/test-view.component.spec.ts index 5583bde0..2b93c7c8 100644 --- a/src/app/group-monitor/test-view/test-view.component.spec.ts +++ b/src/app/group-monitor/test-view/test-view.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Observable, of } from 'rxjs'; import { TestViewComponent } from './test-view.component'; -import {Observable, of} from 'rxjs'; -import {Booklet, BookletError, TestSession} from '../group-monitor.interfaces'; -import {BookletService} from '../booklet.service'; +import { Booklet, BookletError, TestSession } from '../group-monitor.interfaces'; +import { BookletService } from '../booklet.service'; const exampleBooklet: Booklet = { // labels are: {global index}-{ancestor index}-{local index} config: undefined, @@ -42,11 +42,9 @@ const exampleSession: TestSession = { }; class MockBookletService { - public booklets: Observable<Booklet>[] = [of(exampleBooklet)]; public getBooklet(bookletName: string): Observable<Booklet|BookletError> { - if (!bookletName) { return of({'error' : 'general'}); } @@ -61,7 +59,6 @@ class MockBookletService { describe('TestViewComponent', () => { - let component: TestViewComponent; let fixture: ComponentFixture<TestViewComponent>; @@ -87,7 +84,6 @@ describe('TestViewComponent', () => { }); it('should create', () => { - expect(component).toBeTruthy(); }); @@ -135,13 +131,13 @@ describe('TestViewComponent', () => { describe('parseJsonState()', () => { xit('should parse an string containing a state-object', () => { - // TOOD implement unit-test + // TOOD implement unit-test }); }); describe('getMode()', () => { xit('should transform mode-string into label', () => { - // TOOD implement unit-test + // TOOD implement unit-test }); }); diff --git a/src/app/group-monitor/test-view/test-view.component.ts b/src/app/group-monitor/test-view/test-view.component.ts index f8945a62..21b1c76c 100644 --- a/src/app/group-monitor/test-view/test-view.component.ts +++ b/src/app/group-monitor/test-view/test-view.component.ts @@ -1,18 +1,18 @@ -import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange} from '@angular/core'; -import {BookletService} from '../booklet.service'; -import {combineLatest, Observable, Subject, Subscription} from 'rxjs'; import { - Booklet, - TestSession, - Testlet, - Unit, - TestViewDisplayOptions, - BookletError, - UnitContext, isUnit, Selected + Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange +} from '@angular/core'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { + combineLatest, Observable, Subject, Subscription +} from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { BookletService } from '../booklet.service'; +import { + Booklet, TestSession, Testlet, Unit, TestViewDisplayOptions, + BookletError, UnitContext, isUnit, Selected } from '../group-monitor.interfaces'; -import {map} from 'rxjs/operators'; -import {TestMode} from '../../config/test-mode'; -import {MatCheckboxChange} from '@angular/material/checkbox'; +import { TestMode } from '../../config/test-mode'; @Component({ selector: 'tc-test-view', @@ -20,232 +20,224 @@ import {MatCheckboxChange} from '@angular/material/checkbox'; styleUrls: ['./test-view.component.css', './test-view-table.css'] }) export class TestViewComponent implements OnInit, OnChanges, OnDestroy { - @Input() testSession: TestSession; - @Input() displayOptions: TestViewDisplayOptions; - @Input() markedElement: Testlet|Unit|null = null; - @Input() selected: Selected = { - element: undefined, - spreading: false - }; - @Input() checked: boolean; - - @Output() bookletId$ = new EventEmitter<string>(); - @Output() markedElement$ = new EventEmitter<Testlet>(); - @Output() selectedElement$ = new EventEmitter<Selected>(); - @Output() checked$ = new EventEmitter<boolean>(); - - public testSession$: Subject<TestSession> = new Subject<TestSession>(); - public booklet$: Observable<Booklet|BookletError>; - public featuredUnit$: Observable<UnitContext|null>; - - public testletsTimeleft: object|null; // TODO make observable maybe - public testletsClearedCode: object | null; - - private bookletSubscription: Subscription; - - constructor( - private bookletsService: BookletService, - ) { - } - - ngOnInit() { - this.booklet$ = this.bookletsService.getBooklet(this.testSession.bookletName || ''); - - this.bookletSubscription = this.booklet$.subscribe((booklet: Booklet|BookletError) => { - this.bookletId$.emit(this.isBooklet(booklet) ? booklet.metadata.id : ''); - }); - - this.featuredUnit$ = combineLatest<[Booklet|BookletError, TestSession]>([this.booklet$, this.testSession$]) - .pipe(map((bookletAndSession: [Booklet|BookletError, TestSession]): UnitContext|null => { - const booklet: Booklet|BookletError = bookletAndSession[0]; - - if (!this.isBooklet(booklet)) { - return null; - } - - if (this.testSession.unitName) { - return this.getUnitContext(booklet.units, this.testSession.unitName); - } - })); - - // use setTimeout to put this event at the end of js task queue, so testSession$-initialization happens - // after (!) subscription from async-pipe - setTimeout(() => { - this.testSession$.next(this.testSession); - }); - } + @Input() testSession: TestSession; + @Input() displayOptions: TestViewDisplayOptions; + @Input() markedElement: Testlet|Unit|null = null; + @Input() selected: Selected = { + element: undefined, + spreading: false + }; + @Input() checked: boolean; + + @Output() bookletId$ = new EventEmitter<string>(); + @Output() markedElement$ = new EventEmitter<Testlet>(); + @Output() selectedElement$ = new EventEmitter<Selected>(); + @Output() checked$ = new EventEmitter<boolean>(); + + public testSession$: Subject<TestSession> = new Subject<TestSession>(); + public booklet$: Observable<Booklet|BookletError>; + public featuredUnit$: Observable<UnitContext|null>; + + public testletsTimeleft: object|null; // TODO make observable maybe + public testletsClearedCode: object | null; + + private bookletSubscription: Subscription; + + constructor( + private bookletsService: BookletService, + ) { + } + + ngOnInit() { + this.booklet$ = this.bookletsService.getBooklet(this.testSession.bookletName || ''); + + this.bookletSubscription = this.booklet$.subscribe((booklet: Booklet|BookletError) => { + this.bookletId$.emit(this.isBooklet(booklet) ? booklet.metadata.id : ''); + }); + + this.featuredUnit$ = combineLatest<[Booklet|BookletError, TestSession]>([this.booklet$, this.testSession$]) + .pipe(map((bookletAndSession: [Booklet|BookletError, TestSession]): UnitContext|null => { + const booklet: Booklet|BookletError = bookletAndSession[0]; + + if (!this.isBooklet(booklet)) { + return null; + } - ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { - if (typeof changes['testSession'] !== 'undefined') { - this.testSession$.next(this.testSession); - this.testletsTimeleft = this.parseJsonState(this.testSession.testState, 'TESTLETS_TIMELEFT'); - this.testletsClearedCode = this.parseJsonState(this.testSession.testState, 'TESTLETS_CLEARED_CODE'); + if (this.testSession.unitName) { + return this.getUnitContext(booklet.units, this.testSession.unitName); } - } + })); - ngOnDestroy() { - this.bookletSubscription.unsubscribe(); - } + // use setTimeout to put this event at the end of js task queue, so testSession$-initialization happens + // after (!) subscription from async-pipe + setTimeout(() => { + this.testSession$.next(this.testSession); + }); + } - isBooklet(bookletOrBookletError: Booklet|BookletError): bookletOrBookletError is Booklet { - return !('error' in bookletOrBookletError); + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + if (typeof changes['testSession'] !== 'undefined') { + this.testSession$.next(this.testSession); + this.testletsTimeleft = this.parseJsonState(this.testSession.testState, 'TESTLETS_TIMELEFT'); + this.testletsClearedCode = this.parseJsonState(this.testSession.testState, 'TESTLETS_CLEARED_CODE'); } + } - getTestletType(testletOrUnit: Unit|Testlet): 'testlet'|'unit' { - return isUnit(testletOrUnit) ? 'unit' : 'testlet'; - } + ngOnDestroy() { + this.bookletSubscription.unsubscribe(); + } - hasState(state: object, key: string, value: any = null): boolean { - return ((typeof state[key] !== 'undefined') && ((value !== null) ? (state[key] === value) : true)); - } + isBooklet(bookletOrBookletError: Booklet|BookletError): bookletOrBookletError is Booklet { + return !('error' in bookletOrBookletError); + } - stateString(state: object, keys: string[], glue: string = ''): string { - return keys - .map((key: string) => this.hasState(state, key) ? state[key] : null) - .filter((value: string) => value !== null) - .join(glue); - } + getTestletType(testletOrUnit: Unit|Testlet): 'testlet'|'unit' { + return isUnit(testletOrUnit) ? 'unit' : 'testlet'; + } - parseJsonState(testStateObject: object, key: string): object|null { - if (typeof testStateObject[key] === 'undefined') { - return null; - } + hasState(state: object, key: string, value: any = null): boolean { + return ((typeof state[key] !== 'undefined') && ((value !== null) ? (state[key] === value) : true)); + } - const stateValueString = testStateObject[key]; + stateString(state: object, keys: string[], glue: string = ''): string { + return keys + .map((key: string) => this.hasState(state, key) ? state[key] : null) + .filter((value: string) => value !== null) + .join(glue); + } - try { - return JSON.parse(stateValueString); - } catch (error) { - console.warn(`state ${key} is no valid JSON`, stateValueString, error); - return null; - } + parseJsonState(testStateObject: object, key: string): object|null { + if (typeof testStateObject[key] === 'undefined') { + return null; } - getMode(modeString: string): {modeId: string, modeLabel: string} { - const untranslatedModes = ['monitor-group', 'monitor-workspace', 'monitor-study']; - - if (untranslatedModes.indexOf(modeString) > -1) { - - return { - modeId: modeString, - modeLabel: 'Testleiter' - }; - } + const stateValueString = testStateObject[key]; - const testMode = new TestMode(modeString); - return { - modeId: testMode.modeId, - modeLabel: testMode.modeLabel - }; + try { + return JSON.parse(stateValueString); + } catch (error) { + console.warn(`state ${key} is no valid JSON`, stateValueString, error); + return null; } + } - trackUnits(index: number, testlet: Testlet|Unit): string { - return testlet['id'] || index.toString(); - } - - getUnitContext(testlet: Testlet, unitName: String, level: number = 0, countGlobal = 0, - countAncestor = 0, ancestor: Testlet = null): UnitContext { - - let result: UnitContext = { - unit: null, - parent: null, - ancestor: (level <= 1) ? testlet : ancestor, - unitCount: 0, - unitCountGlobal: countGlobal, - unitCountAncestor: countAncestor, - indexGlobal: -1, - indexLocal: -1, - indexAncestor: -1, - }; - - let i = -1; - while (i++ < testlet.children.length - 1) { - - const testletOrUnit = testlet.children[i]; - - if (isUnit(testletOrUnit)) { - - if (testletOrUnit.id === unitName) { + getMode(modeString: string): {modeId: string, modeLabel: string} { + const untranslatedModes = ['monitor-group', 'monitor-workspace', 'monitor-study']; - result.indexGlobal = result.unitCountGlobal; - result.indexLocal = result.unitCount; - result.indexAncestor = result.unitCountAncestor; - result.unit = testletOrUnit; - result.parent = testlet; - } - - result.unitCount++; - result.unitCountGlobal++; - result.unitCountAncestor++; - - } else { - - const subResult = this.getUnitContext(testletOrUnit, unitName, level + 1, result.unitCountGlobal, - (level < 1) ? 0 : result.unitCountAncestor, result.ancestor); - result.unitCountGlobal = subResult.unitCountGlobal; - result.unitCountAncestor = (level < 1) ? result.unitCountAncestor : subResult.unitCountAncestor; - - if (subResult.indexLocal >= 0) { - result = subResult; - } - } - } - - return result; + if (untranslatedModes.indexOf(modeString) > -1) { + return { + modeId: modeString, + modeLabel: 'Testleiter' + }; } - mark(testletOrUnit: Testlet|Unit|null = null) { - if (testletOrUnit == null) { - this.markedElement = null; - this.markedElement$.emit(null); - } else if (isUnit(testletOrUnit) && this.displayOptions.selectionMode === 'unit') { - this.markedElement = testletOrUnit; - } else if (!isUnit(testletOrUnit) && this.displayOptions.selectionMode === 'block') { - this.markedElement$.emit(testletOrUnit); - this.markedElement = testletOrUnit; - } - } + const testMode = new TestMode(modeString); + return { + modeId: testMode.modeId, + modeLabel: testMode.modeLabel + }; + } + + trackUnits(index: number, testlet: Testlet|Unit): string { + return testlet['id'] || index.toString(); + } + + getUnitContext(testlet: Testlet, unitName: String, level: number = 0, countGlobal = 0, + countAncestor = 0, ancestor: Testlet = null): UnitContext { + let result: UnitContext = { + unit: null, + parent: null, + ancestor: (level <= 1) ? testlet : ancestor, + unitCount: 0, + unitCountGlobal: countGlobal, + unitCountAncestor: countAncestor, + indexGlobal: -1, + indexLocal: -1, + indexAncestor: -1, + }; - select($event: Event, testletOrUnit: Testlet|Unit|null) { - if ((isUnit(testletOrUnit) ? 'unit' : 'block') !== this.displayOptions.selectionMode) { - return; + let i = -1; + while (i++ < testlet.children.length - 1) { + const testletOrUnit = testlet.children[i]; + + if (isUnit(testletOrUnit)) { + if (testletOrUnit.id === unitName) { + result.indexGlobal = result.unitCountGlobal; + result.indexLocal = result.unitCount; + result.indexAncestor = result.unitCountAncestor; + result.unit = testletOrUnit; + result.parent = testlet; } - $event.stopPropagation(); + result.unitCount++; + result.unitCountGlobal++; + result.unitCountAncestor++; - this.applySelection(testletOrUnit); - } + } else { + const subResult = this.getUnitContext(testletOrUnit, unitName, level + 1, result.unitCountGlobal, + (level < 1) ? 0 : result.unitCountAncestor, result.ancestor); + result.unitCountGlobal = subResult.unitCountGlobal; + result.unitCountAncestor = (level < 1) ? result.unitCountAncestor : subResult.unitCountAncestor; - deselect($event: MouseEvent|null) { - if ($event && ($event.currentTarget === $event.target)) { - this.applySelection(); + if (subResult.indexLocal >= 0) { + result = subResult; } - } - - deselectForce($event: Event): boolean { - this.applySelection(); - $event.stopImmediatePropagation(); - $event.stopPropagation(); - $event.preventDefault(); - return false; - } - - invertSelectionTestheftWide(): boolean { - this.applySelection(this.selected.element, true); - return false; - } - - private applySelection(testletOrUnit: Testlet|Unit|null = null, inversion: boolean = false) { - this.selected = { - element: testletOrUnit, - session: this.testSession, - spreading: (this.selected?.element?.id === testletOrUnit?.id) && !inversion ? !this.selected?.spreading : true, - inversion - }; - this.selectedElement$.emit(this.selected); - } + } + } + return result; + } + + mark(testletOrUnit: Testlet|Unit|null = null) { + if (testletOrUnit == null) { + this.markedElement = null; + this.markedElement$.emit(null); + } else if (isUnit(testletOrUnit) && this.displayOptions.selectionMode === 'unit') { + this.markedElement = testletOrUnit; + } else if (!isUnit(testletOrUnit) && this.displayOptions.selectionMode === 'block') { + this.markedElement$.emit(testletOrUnit); + this.markedElement = testletOrUnit; + } + } + + select($event: Event, testletOrUnit: Testlet|Unit|null) { + if ((isUnit(testletOrUnit) ? 'unit' : 'block') !== this.displayOptions.selectionMode) { + return; + } + + $event.stopPropagation(); + this.applySelection(testletOrUnit); + } + + deselect($event: MouseEvent|null) { + if ($event && ($event.currentTarget === $event.target)) { + this.applySelection(); + } + } + + deselectForce($event: Event): boolean { + this.applySelection(); + $event.stopImmediatePropagation(); + $event.stopPropagation(); + $event.preventDefault(); + return false; + } + + invertSelectionTestheftWide(): boolean { + this.applySelection(this.selected.element, true); + return false; + } + + private applySelection(testletOrUnit: Testlet|Unit|null = null, inversion: boolean = false) { + this.selected = { + element: testletOrUnit, + session: this.testSession, + spreading: (this.selected?.element?.id === testletOrUnit?.id) && !inversion ? !this.selected?.spreading : true, + inversion + }; + this.selectedElement$.emit(this.selected); + } - check($event: MatCheckboxChange) { - this.checked$.emit($event.checked); - } + check($event: MatCheckboxChange) { + this.checked$.emit($event.checked); + } } diff --git a/src/app/maindata.service.spec.ts b/src/app/maindata.service.spec.ts index 2a25eaee..666f201b 100644 --- a/src/app/maindata.service.spec.ts +++ b/src/app/maindata.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; import { MainDataService } from './maindata.service'; -import {HttpClientModule} from "@angular/common/http"; describe('MainDataService', () => { beforeEach(() => { diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts index 6e1feb10..2bfa502c 100644 --- a/src/app/maindata.service.ts +++ b/src/app/maindata.service.ts @@ -1,12 +1,12 @@ -import {BackendService} from './backend.service'; -import {BehaviorSubject, Subject} from 'rxjs'; -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { CustomtextService } from 'iqb-components'; import { AppError, AuthData, KeyValuePairs } from './app.interfaces'; -import {CustomtextService} from 'iqb-components'; -import {AppConfig} from './config/app.config'; +import { BackendService } from './backend.service'; +import { AppConfig } from './config/app.config'; const localStorageAuthDataKey = 'iqb-tc-a'; const localStorageTestConfigKey = 'iqb-tc-c'; @@ -78,11 +78,11 @@ export class MainDataService { this.appConfig = new AppConfig(cts); } - setSpinnerOn() { + setSpinnerOn(): void { this.isSpinnerOn$.next(true); } - setSpinnerOff() { + setSpinnerOff(): void { this.isSpinnerOn$.next(false); } diff --git a/src/app/sys-check/backend.service.ts b/src/app/sys-check/backend.service.ts index dc1c5ec9..70cff6b3 100644 --- a/src/app/sys-check/backend.service.ts +++ b/src/app/sys-check/backend.service.ts @@ -83,7 +83,6 @@ export class BackendService { if (xhr.status !== 200) { testResult.error = `Error ${xhr.statusText} (${xhr.status}) `; } - // tslint:disable-next-line:triple-equals if (xhr.response.toString().length != requestedDownloadSize) { testResult.error = `Error: Data package has wrong size! ${requestedDownloadSize} ` + xhr.response.toString().length; } @@ -146,7 +145,6 @@ export class BackendService { const response = JSON.parse(xhr.response); const arrivingSize = parseFloat(response['packageReceivedSize']); - // tslint:disable-next-line:triple-equals if (arrivingSize != requestedUploadSize) { testResult.error = `Error: Data package has wrong size! ${requestedUploadSize} != ${arrivingSize}`; } @@ -177,7 +175,6 @@ export class BackendService { } - // tslint:disable-next-line:member-ordering private static getMostPreciseTimestampBrowserCanProvide(): number { if (typeof performance !== 'undefined') { const timeOrigin = (typeof performance.timeOrigin !== 'undefined') ? performance.timeOrigin : performance.timing.navigationStart; @@ -189,7 +186,6 @@ export class BackendService { } - // tslint:disable-next-line:member-ordering private static generateRandomContent(length: number): string { const base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz0123456789+/'; let randomString = ''; @@ -200,4 +196,3 @@ export class BackendService { return randomString; } } - diff --git a/src/app/sys-check/network-check/network-check.component.ts b/src/app/sys-check/network-check/network-check.component.ts index 140a0f76..b310d0bd 100644 --- a/src/app/sys-check/network-check/network-check.component.ts +++ b/src/app/sys-check/network-check/network-check.component.ts @@ -40,7 +40,6 @@ export class NetworkCheckComponent implements OnInit, OnDestroy { }; private humanReadableMilliseconds = (milliseconds: number): string => (milliseconds / 1000).toString() + ' sec'; - // tslint:disable-next-line:member-ordering private static calculateAverageSpeedBytePerSecond(testResults: Array<NetworkRequestTestResult>): number { return testResults.reduce((sum, result) => sum + (result.size / (result.duration / 1000)), 0) / testResults.length; } diff --git a/src/app/sys-check/welcome/welcome.component.ts b/src/app/sys-check/welcome/welcome.component.ts index d5fa8160..d1119962 100644 --- a/src/app/sys-check/welcome/welcome.component.ts +++ b/src/app/sys-check/welcome/welcome.component.ts @@ -50,7 +50,6 @@ export class WelcomeComponent implements OnInit { private getBrowser() { const userAgent = window.navigator.userAgent; - // tslint:disable-next-line:max-line-length const regex = /(MSIE|Trident|(?!Gecko.+)Firefox|(?!AppleWebKit.+Chrome.+)Safari(?!.+Edge)|(?!AppleWebKit.+)Chrome(?!.+Edge)|(?!AppleWebKit.+Chrome.+Safari.+)Edge|AppleWebKit(?!.+Chrome|.+Safari)|Gecko(?!.+Firefox))(?: |\/)([\d\.apre]+)/; // credit due to: https://gist.github.com/ticky/3909462#gistcomment-2245669 const deviceInfoSplits = regex.exec(userAgent); diff --git a/src/app/workspace-admin/backend.service.spec.ts b/src/app/workspace-admin/backend.service.spec.ts index fab66224..e398c819 100644 --- a/src/app/workspace-admin/backend.service.spec.ts +++ b/src/app/workspace-admin/backend.service.spec.ts @@ -1,9 +1,8 @@ import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; import { BackendService } from './backend.service'; -import {HttpClientModule} from '@angular/common/http'; -import {WorkspaceDataService} from './workspacedata.service'; - +import { WorkspaceDataService } from './workspacedata.service'; describe('HttpClient testing', () => { beforeEach(() => { diff --git a/src/app/workspace-admin/backend.service.ts b/src/app/workspace-admin/backend.service.ts index 7646f48e..283ddbb2 100644 --- a/src/app/workspace-admin/backend.service.ts +++ b/src/app/workspace-admin/backend.service.ts @@ -1,17 +1,18 @@ -import {GetFileResponseData, CheckWorkspaceResponseData, SysCheckStatistics, - ReviewData, LogData, UnitResponse, ResultData} from './workspace.interfaces'; -import {Injectable, Inject} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {Observable, of} from 'rxjs'; -import {catchError, map} from 'rxjs/operators'; -import {WorkspaceDataService} from './workspacedata.service'; -import {ApiError, WorkspaceData} from '../app.interfaces'; +import { Injectable, Inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { + GetFileResponseData, CheckWorkspaceResponseData, SysCheckStatistics, + ReviewData, LogData, UnitResponse, ResultData +} from './workspace.interfaces'; +import { WorkspaceDataService } from './workspacedata.service'; +import { ApiError, WorkspaceData } from '../app.interfaces'; @Injectable({ providedIn: 'root' }) export class BackendService { - constructor( @Inject('SERVER_URL') private readonly serverUrl: string, private wds: WorkspaceDataService, diff --git a/src/app/workspace-admin/files/files.component.spec.ts b/src/app/workspace-admin/files/files.component.spec.ts index 53af9b0f..d4f9b20d 100644 --- a/src/app/workspace-admin/files/files.component.spec.ts +++ b/src/app/workspace-admin/files/files.component.spec.ts @@ -1,12 +1,12 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { FilesComponent } from './files.component'; -import {HttpClientModule} from '@angular/common/http'; -import {BackendService} from '../backend.service'; -import {WorkspaceDataService} from '../workspacedata.service'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatSnackBarModule} from '@angular/material/snack-bar'; -import {MainDataService} from '../../maindata.service'; +import { BackendService } from '../backend.service'; +import { WorkspaceDataService } from '../workspacedata.service'; +import { MainDataService } from '../../maindata.service'; describe('FilesComponent', () => { let component: FilesComponent; @@ -14,7 +14,7 @@ describe('FilesComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ FilesComponent ], + declarations: [FilesComponent], imports: [ HttpClientModule, MatDialogModule, @@ -26,7 +26,7 @@ describe('FilesComponent', () => { MainDataService ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/workspace-admin/files/files.component.ts b/src/app/workspace-admin/files/files.component.ts index 9839853b..fc6b33ee 100644 --- a/src/app/workspace-admin/files/files.component.ts +++ b/src/app/workspace-admin/files/files.component.ts @@ -1,16 +1,20 @@ -import { WorkspaceDataService } from '../workspacedata.service'; -import { GetFileResponseData, CheckWorkspaceResponseData } from '../workspace.interfaces'; -import { ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent, - MessageDialogData, MessageType } from 'iqb-components'; +import { + Component, OnInit, Inject, ViewChild +} from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { MatSnackBar } from '@angular/material/snack-bar'; -import {BackendService, FileDeletionReport} from '../backend.service'; -import { Component, OnInit, Inject } from '@angular/core'; -import { ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; + import { saveAs } from 'file-saver'; -import {MainDataService} from '../../maindata.service'; +import { + ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent, + MessageDialogData, MessageType +} from 'iqb-components'; +import { WorkspaceDataService } from '../workspacedata.service'; +import { GetFileResponseData, CheckWorkspaceResponseData } from '../workspace.interfaces'; +import { BackendService, FileDeletionReport } from '../backend.service'; +import { MainDataService } from '../../maindata.service'; @Component({ templateUrl: './files.component.html', @@ -41,7 +45,7 @@ export class FilesComponent implements OnInit { public snackBar: MatSnackBar ) { } - ngOnInit() { + ngOnInit(): void { this.uploadUrl = `${this.serverUrl}workspace/${this.wds.wsId}/file`; setTimeout(() => { this.mds.setSpinnerOn(); @@ -50,7 +54,7 @@ export class FilesComponent implements OnInit { } checkAll(isChecked: boolean) { - this.serverfiles.data.forEach(element => { + this.serverfiles.data.forEach((element) => { element.isChecked = isChecked; }); } @@ -62,9 +66,9 @@ export class FilesComponent implements OnInit { this.checkInfos = []; const filesToDelete = []; - this.serverfiles.data.forEach(element => { + this.serverfiles.data.forEach((element) => { if (element.isChecked) { - filesToDelete.push(element.type + '/' + element.filename); + filesToDelete.push(`${element.type}/${element.filename}`); } }); @@ -79,7 +83,7 @@ export class FilesComponent implements OnInit { width: '400px', data: <ConfirmDialogData>{ title: 'Löschen von Dateien', - content: prompt + ' diese gelöscht werden?', + content: `${prompt} diese gelöscht werden?`, confirmbuttonlabel: 'Löschen', showcancel: true } @@ -114,8 +118,7 @@ export class FilesComponent implements OnInit { } } - - updateFileList(empty = false) { + updateFileList(empty = false): void { this.checkErrors = []; this.checkWarnings = []; this.checkInfos = []; @@ -147,7 +150,7 @@ export class FilesComponent implements OnInit { ); } - checkWorkspace() { + checkWorkspace(): void { this.checkErrors = []; this.checkWarnings = []; this.checkInfos = []; diff --git a/src/app/workspace-admin/files/iqb-files/iqb-files.module.ts b/src/app/workspace-admin/files/iqb-files/iqb-files.module.ts index 49b63fb5..3e315635 100644 --- a/src/app/workspace-admin/files/iqb-files/iqb-files.module.ts +++ b/src/app/workspace-admin/files/iqb-files/iqb-files.module.ts @@ -1,15 +1,14 @@ import { NgModule } from '@angular/core'; -import { IqbFilesUploadComponent } from './iqbFilesUpload/iqbFilesUpload.component'; -import { IqbFilesUploadQueueComponent } from './iqbFilesUploadQueue/iqbFilesUploadQueue.component'; -import { IqbFilesUploadInputForDirective } from './iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive'; - import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatIconModule } from '@angular/material/icon'; import { CommonModule } from '@angular/common'; -import { IqbComponentsModule } from 'iqb-components'; +import { IqbComponentsModule } from 'iqb-components'; +import { IqbFilesUploadComponent } from './iqbFilesUpload/iqbFilesUpload.component'; +import { IqbFilesUploadQueueComponent } from './iqbFilesUploadQueue/iqbFilesUploadQueue.component'; +import { IqbFilesUploadInputForDirective } from './iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive'; @NgModule({ imports: [ @@ -27,7 +26,7 @@ import { IqbComponentsModule } from 'iqb-components'; ], exports: [ IqbFilesUploadQueueComponent, - IqbFilesUploadInputForDirective, + IqbFilesUploadInputForDirective ] }) export class IqbFilesModule { } diff --git a/src/app/workspace-admin/files/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts b/src/app/workspace-admin/files/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts index eab15d54..39c2706c 100644 --- a/src/app/workspace-admin/files/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts +++ b/src/app/workspace-admin/files/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts @@ -1,101 +1,102 @@ -import { Component, EventEmitter, OnDestroy, QueryList, ViewChildren, Input, Output } from '@angular/core'; -import { IqbFilesUploadComponent, UploadStatus } from '../iqbFilesUpload/iqbFilesUpload.component'; +import { + Component, EventEmitter, OnDestroy, QueryList, ViewChildren, Input, Output +} from '@angular/core'; import { HttpHeaders, HttpParams } from '@angular/common/http'; - +import { IqbFilesUploadComponent, UploadStatus } from '../iqbFilesUpload/iqbFilesUpload.component'; /** * A material design file upload queue component. */ @Component({ - selector: 'iqb-files-upload-queue', - templateUrl: `iqbFilesUploadQueue.component.html`, - exportAs: 'iqbFilesUploadQueue', - }) - export class IqbFilesUploadQueueComponent implements OnDestroy { + selector: 'iqb-files-upload-queue', + templateUrl: 'iqbFilesUploadQueue.component.html', + exportAs: 'iqbFilesUploadQueue' +}) +export class IqbFilesUploadQueueComponent implements OnDestroy { + @ViewChildren(IqbFilesUploadComponent) fileUploads: QueryList<IqbFilesUploadComponent>; - @ViewChildren(IqbFilesUploadComponent) fileUploads: QueryList<IqbFilesUploadComponent>; + public files: Array<any> = []; - public files: Array<any> = []; - public disableClearButton = true; + public disableClearButton = true; - /* Http request input bindings */ - @Input() - httpUrl: string; + /* Http request input bindings */ + @Input() + httpUrl: string; - @Input() - httpRequestHeaders: HttpHeaders | { - [header: string]: string | string[]; - } = new HttpHeaders().set('Content-Type', 'multipart/form-data'); + @Input() + httpRequestHeaders: HttpHeaders | { + [header: string]: string | string[]; + } = new HttpHeaders().set('Content-Type', 'multipart/form-data'); - @Input() - httpRequestParams: HttpParams | { - [param: string]: string | string[]; - } = new HttpParams(); + @Input() + httpRequestParams: HttpParams | { + [param: string]: string | string[]; + } = new HttpParams(); - @Input() - fileAlias: string; + @Input() + fileAlias: string; - @Input() - tokenName: string; + @Input() + tokenName: string; - @Input() - token: string; + @Input() + token: string; - @Input() - folderName: string; + @Input() + folderName: string; - @Input() - folder: string; + @Input() + folder: string; - @Output() uploadCompleteEvent = new EventEmitter<IqbFilesUploadQueueComponent>(); + @Output() uploadCompleteEvent = new EventEmitter<IqbFilesUploadQueueComponent>(); - add(file: any) { - this.files.push(file); - } + add(file: any) { + this.files.push(file); + } - public removeAll() { - this.files.splice(0, this.files.length); - } + public removeAll() { + this.files.splice(0, this.files.length); + } - ngOnDestroy() { - if (this.files) { - this.removeAll(); - } + ngOnDestroy() { + if (this.files) { + this.removeAll(); } + } - removeFile(fileToRemove: IqbFilesUploadComponent) { - this.files.splice(fileToRemove.id, 1); - } + removeFile(fileToRemove: IqbFilesUploadComponent) { + this.files.splice(fileToRemove.id, 1); + } /* - updateStatus() { - this.numberOfErrors = 0; - this.numberOfUploads = 0; - - this.fileUploads.forEach((fileUpload) => { - - fileUpload.upload(); - }); - } */ - - analyseStatus() { - let someoneiscomplete = false; - let someoneisbusy = false; - let someoneisready = false; - this.fileUploads.forEach((fileUpload) => { - if ((fileUpload.status === UploadStatus.ok) || (fileUpload.status === UploadStatus.error)) { - someoneiscomplete = true; - } else if (fileUpload.status === UploadStatus.busy) { - someoneisbusy = true; - return; // forEach - } else if (fileUpload.status === UploadStatus.ready) { - someoneisready = true; - } - }); - - if (someoneiscomplete && !someoneisbusy) { - this.uploadCompleteEvent.emit(); - this.disableClearButton = false; + updateStatus() { + this.numberOfErrors = 0; + this.numberOfUploads = 0; + + this.fileUploads.forEach((fileUpload) => { + + fileUpload.upload(); + }); + } */ + + analyseStatus() { + let someoneiscomplete = false; + let someoneisbusy = false; + let someoneisready = false; + this.fileUploads.forEach((fileUpload) => { + if ((fileUpload.status === UploadStatus.ok) || (fileUpload.status === UploadStatus.error)) { + someoneiscomplete = true; + } else if (fileUpload.status === UploadStatus.busy) { + someoneisbusy = true; + return; // forEach + } else if (fileUpload.status === UploadStatus.ready) { + someoneisready = true; } + }); + + if (someoneiscomplete && !someoneisbusy) { + this.uploadCompleteEvent.emit(); + this.disableClearButton = false; } + } } diff --git a/src/app/workspace-admin/results/results.component.spec.ts b/src/app/workspace-admin/results/results.component.spec.ts index 5ca49495..f6f9f6d9 100644 --- a/src/app/workspace-admin/results/results.component.spec.ts +++ b/src/app/workspace-admin/results/results.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { ResultsComponent } from './results.component'; -import {HttpClientModule} from '@angular/common/http'; -import {BackendService} from '../backend.service'; -import {WorkspaceDataService} from '../workspacedata.service'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatSnackBarModule} from '@angular/material/snack-bar'; +import { BackendService } from '../backend.service'; +import { WorkspaceDataService } from '../workspacedata.service'; describe('ResultsComponent', () => { let component: ResultsComponent; @@ -13,7 +13,7 @@ describe('ResultsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ResultsComponent ], + declarations: [ResultsComponent], imports: [ HttpClientModule, MatDialogModule, @@ -24,7 +24,7 @@ describe('ResultsComponent', () => { WorkspaceDataService ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/workspace-admin/results/results.component.ts b/src/app/workspace-admin/results/results.component.ts index 3125b27b..6c71d07c 100644 --- a/src/app/workspace-admin/results/results.component.ts +++ b/src/app/workspace-admin/results/results.component.ts @@ -1,6 +1,6 @@ import { LogData } from '../workspace.interfaces'; import { WorkspaceDataService } from '../workspacedata.service'; -import {ConfirmDialogComponent, ConfirmDialogData} from 'iqb-components'; +import { ConfirmDialogComponent, ConfirmDialogData } from 'iqb-components'; import { Component, OnInit, ViewChild } from '@angular/core'; import { BackendService } from '../backend.service'; import { MatDialog } from '@angular/material/dialog'; @@ -10,8 +10,7 @@ import { MatTableDataSource } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; import { saveAs } from 'file-saver'; import { ResultData, UnitResponse, ReviewData } from '../workspace.interfaces'; -import {MainDataService} from '../../maindata.service'; - +import { MainDataService } from '../../maindata.service'; @Component({ templateUrl: './results.component.html', @@ -242,20 +241,20 @@ export class ResultsComponent implements OnInit { width: '400px', data: <ConfirmDialogData>{ title: 'Löschen von Gruppendaten', - content: prompt + 'gelöscht. Fortsetzen?', + content: `${prompt}gelöscht. Fortsetzen?`, confirmbuttonlabel: 'Gruppendaten löschen', showcancel: true } }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { if (result !== false) { this.mds.setSpinnerOn(); this.bs.deleteData(selectedGroups).subscribe((ok: boolean) => { if (ok) { - this.snackBar.open('Löschen erfolgreich.', 'Ok.', {duration: 3000}); + this.snackBar.open('Löschen erfolgreich.', 'Ok.', { duration: 3000 }); } else { - this.snackBar.open('Löschen nicht erfolgreich.', 'Fehler', {duration: 3000}); + this.snackBar.open('Löschen nicht erfolgreich.', 'Fehler', { duration: 3000 }); } this.tableselectionCheckbox.clear(); this.updateTable(); diff --git a/src/app/workspace-admin/syscheck/syscheck.component.spec.ts b/src/app/workspace-admin/syscheck/syscheck.component.spec.ts index 420681c3..ef577526 100644 --- a/src/app/workspace-admin/syscheck/syscheck.component.spec.ts +++ b/src/app/workspace-admin/syscheck/syscheck.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { SyscheckComponent } from './syscheck.component'; -import {HttpClientModule} from '@angular/common/http'; -import {BackendService} from '../backend.service'; -import {MatDialogModule} from '@angular/material/dialog'; -import {MatSnackBarModule} from '@angular/material/snack-bar'; -import {WorkspaceDataService} from '../workspacedata.service'; +import { BackendService } from '../backend.service'; +import { WorkspaceDataService } from '../workspacedata.service'; describe('Workspace-Admin: SyscheckComponent', () => { let component: SyscheckComponent; @@ -13,7 +13,7 @@ describe('Workspace-Admin: SyscheckComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ SyscheckComponent ], + declarations: [SyscheckComponent], imports: [ HttpClientModule, MatDialogModule, @@ -24,7 +24,7 @@ describe('Workspace-Admin: SyscheckComponent', () => { WorkspaceDataService ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/workspace-admin/syscheck/syscheck.component.ts b/src/app/workspace-admin/syscheck/syscheck.component.ts index e8f5916b..b29e6d3d 100644 --- a/src/app/workspace-admin/syscheck/syscheck.component.ts +++ b/src/app/workspace-admin/syscheck/syscheck.component.ts @@ -1,15 +1,15 @@ -import {ConfirmDialogComponent, ConfirmDialogData} from 'iqb-components'; import { Component, OnInit, ViewChild } from '@angular/core'; -import { BackendService } from '../backend.service'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; + import { saveAs } from 'file-saver'; +import { ConfirmDialogComponent, ConfirmDialogData } from 'iqb-components'; +import { BackendService } from '../backend.service'; import { SysCheckStatistics } from '../workspace.interfaces'; -import {MainDataService} from '../../maindata.service'; - +import { MainDataService } from '../../maindata.service'; @Component({ templateUrl: './syscheck.component.html', @@ -49,13 +49,13 @@ export class SyscheckComponent implements OnInit { ); } - isAllSelected() { + isAllSelected(): void { const numSelected = this.tableselectionCheckbox.selected.length; const numRows = this.resultDataSource.data.length; return numSelected === numRows; } - masterToggle() { + masterToggle(): void { this.isAllSelected() ? this.tableselectionCheckbox.clear() : this.resultDataSource.data.forEach(row => this.tableselectionCheckbox.select(row)); @@ -91,13 +91,13 @@ export class SyscheckComponent implements OnInit { deleteReports() { if (this.tableselectionCheckbox.selected.length > 0) { const selectedReports: string[] = []; - this.tableselectionCheckbox.selected.forEach(element => { + this.tableselectionCheckbox.selected.forEach((element) => { selectedReports.push(element.id); }); let prompt = 'Es werden alle Berichte für diese'; if (selectedReports.length > 1) { - prompt = prompt + ' ' + selectedReports.length + ' System-Checks '; + prompt = `${prompt} ${selectedReports.length} System-Checks `; } else { prompt = prompt + 'n System-Check "' + selectedReports[0] + '" '; } @@ -106,24 +106,24 @@ export class SyscheckComponent implements OnInit { width: '400px', data: <ConfirmDialogData>{ title: 'Löschen von Berichten', - content: prompt + 'gelöscht. Fortsetzen?', + content: `${prompt}gelöscht. Fortsetzen?`, confirmbuttonlabel: 'Berichtsdaten löschen', showcancel: true } }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { if (result !== false) { this.mds.setSpinnerOn(); this.bs.deleteSysCheckReports(selectedReports).subscribe((fileDeletionReport) => { const message = []; if (fileDeletionReport.deleted.length > 0) { - message.push(fileDeletionReport.deleted.length + ' Berichte erfolgreich gelöscht.'); + message.push(`${fileDeletionReport.deleted.length} Berichte erfolgreich gelöscht.`); } if (fileDeletionReport.not_allowed.length > 0) { - message.push(fileDeletionReport.not_allowed.length + ' Berichte konnten nicht gelöscht werden.'); + message.push(`${fileDeletionReport.not_allowed.length} Berichte konnten nicht gelöscht werden.`); } - this.snackBar.open(message.join('<br>'), message.length > 1 ? 'Achtung' : '', {duration: 1000}); + this.snackBar.open(message.join('<br>'), message.length > 1 ? 'Achtung' : '', { duration: 1000 }); this.updateTable(); }); } diff --git a/src/app/workspace-admin/workspace-routing.module.ts b/src/app/workspace-admin/workspace-routing.module.ts index c01cc7d4..c68f41b0 100644 --- a/src/app/workspace-admin/workspace-routing.module.ts +++ b/src/app/workspace-admin/workspace-routing.module.ts @@ -1,7 +1,8 @@ -import { SyscheckComponent } from './syscheck/syscheck.component'; -import { ResultsComponent } from './results/results.component'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; + +import { SyscheckComponent } from './syscheck/syscheck.component'; +import { ResultsComponent } from './results/results.component'; import { FilesComponent } from './files/files.component'; import { WorkspaceComponent } from './workspace.component'; @@ -10,15 +11,14 @@ const routes: Routes = [ path: ':ws', component: WorkspaceComponent, children: [ - {path: '', redirectTo: 'monitor', pathMatch: 'full'}, - {path: 'files', component: FilesComponent}, - {path: 'syscheck', component: SyscheckComponent}, - {path: 'results', component: ResultsComponent}, - {path: '**', component: FilesComponent} + { path: '', redirectTo: 'monitor', pathMatch: 'full' }, + { path: 'files', component: FilesComponent }, + { path: 'syscheck', component: SyscheckComponent }, + { path: 'results', component: ResultsComponent }, + { path: '**', component: FilesComponent } ] }]; - @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] diff --git a/src/app/workspace-admin/workspace.component.ts b/src/app/workspace-admin/workspace.component.ts index 8cc26bef..e252a541 100644 --- a/src/app/workspace-admin/workspace.component.ts +++ b/src/app/workspace-admin/workspace.component.ts @@ -1,9 +1,8 @@ -import { WorkspaceDataService } from './workspacedata.service'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; -import { Component, OnInit, OnDestroy } from '@angular/core'; -import {BackendService} from './backend.service'; - +import { WorkspaceDataService } from './workspacedata.service'; +import { BackendService } from './backend.service'; @Component({ templateUrl: './workspace.component.html', @@ -18,21 +17,19 @@ export class WorkspaceComponent implements OnInit, OnDestroy { public wds: WorkspaceDataService ) { } - ngOnInit() { - this.routingSubscription = this.route.params.subscribe(params => { + ngOnInit(): void { + this.routingSubscription = this.route.params.subscribe((params) => { this.wds.wsId = params['ws']; - this.bs.getWorkspaceData(this.wds.wsId).subscribe( - wsData => { - if (typeof wsData !== 'number') { - this.wds.wsName = wsData.name; - this.wds.wsRole = wsData.role; - } + this.bs.getWorkspaceData(this.wds.wsId).subscribe((wsData) => { + if (typeof wsData !== 'number') { + this.wds.wsName = wsData.name; + this.wds.wsRole = wsData.role; } - ); + }); }); } - ngOnDestroy() { + ngOnDestroy(): void { if (this.routingSubscription !== null) { this.routingSubscription.unsubscribe(); } diff --git a/src/app/workspace-admin/workspace.interfaces.ts b/src/app/workspace-admin/workspace.interfaces.ts index d8d9e049..3aa5fdad 100644 --- a/src/app/workspace-admin/workspace.interfaces.ts +++ b/src/app/workspace-admin/workspace.interfaces.ts @@ -44,7 +44,7 @@ export interface UnitResponse { bookletname: string; unitname: string; responses: string; - restorepoint: string; + restorepoint: string; responsetype: string; responses_ts: number; restorepoint_ts: number; diff --git a/src/app/workspace-admin/workspace.module.spec.ts b/src/app/workspace-admin/workspace.module.spec.ts index bc772017..b88c8018 100644 --- a/src/app/workspace-admin/workspace.module.spec.ts +++ b/src/app/workspace-admin/workspace.module.spec.ts @@ -1,4 +1,4 @@ -import {WorkspaceModule} from "./workspace.module"; +import { WorkspaceModule } from './workspace.module'; describe('WorkspaceModule', () => { let workspaceModule: WorkspaceModule; diff --git a/src/app/workspace-admin/workspace.module.ts b/src/app/workspace-admin/workspace.module.ts index 3fbecf6e..8f5ed6a8 100644 --- a/src/app/workspace-admin/workspace.module.ts +++ b/src/app/workspace-admin/workspace.module.ts @@ -1,15 +1,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; -import { BackendService } from './backend.service'; import { ReactiveFormsModule } from '@angular/forms'; -import { NgModule} from '@angular/core'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { WorkspaceDataService } from './workspacedata.service'; - -import { WorkspaceRoutingModule } from './workspace-routing.module'; -import { WorkspaceComponent } from './workspace.component'; -import { FilesComponent } from './files/files.component'; -import { ResultsComponent } from './results/results.component'; - import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -27,9 +19,16 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatGridListModule } from '@angular/material/grid-list'; -import { SyscheckComponent } from './syscheck/syscheck.component'; + import { IqbComponentsModule } from 'iqb-components'; -import {IqbFilesModule} from './files/iqb-files'; +import { BackendService } from './backend.service'; +import { WorkspaceDataService } from './workspacedata.service'; +import { WorkspaceRoutingModule } from './workspace-routing.module'; +import { WorkspaceComponent } from './workspace.component'; +import { FilesComponent } from './files/files.component'; +import { ResultsComponent } from './results/results.component'; +import { SyscheckComponent } from './syscheck/syscheck.component'; +import { IqbFilesModule } from './files/iqb-files'; @NgModule({ imports: [ @@ -70,7 +69,7 @@ import {IqbFilesModule} from './files/iqb-files'; providers: [ BackendService, WorkspaceDataService - ], + ] }) export class WorkspaceModule { } diff --git a/src/app/workspace-admin/workspacedata.service.spec.ts b/src/app/workspace-admin/workspacedata.service.spec.ts index 95f8d42c..f7450008 100644 --- a/src/app/workspace-admin/workspacedata.service.spec.ts +++ b/src/app/workspace-admin/workspacedata.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; import { WorkspaceDataService } from './workspacedata.service'; -import {HttpClientModule} from "@angular/common/http"; describe('WorkspaceDataService', () => { beforeEach(() => { diff --git a/src/app/workspace-admin/workspacedata.service.ts b/src/app/workspace-admin/workspacedata.service.ts index c4c7a63f..0cee9100 100644 --- a/src/app/workspace-admin/workspacedata.service.ts +++ b/src/app/workspace-admin/workspacedata.service.ts @@ -11,8 +11,8 @@ export class WorkspaceDataService { public wsName = ''; public navLinks = [ - {path: 'files', label: 'Dateien'}, - {path: 'syscheck', label: 'System-Check Berichte'}, - {path: 'results', label: 'Ergebnisse/Antworten'} + { path: 'files', label: 'Dateien' }, + { path: 'syscheck', label: 'System-Check Berichte' }, + { path: 'results', label: 'Ergebnisse/Antworten' } ]; } diff --git a/src/app/workspace-monitor/backend.service.spec.ts b/src/app/workspace-monitor/backend.service.spec.ts index ffdc1397..e4f50fbf 100644 --- a/src/app/workspace-monitor/backend.service.spec.ts +++ b/src/app/workspace-monitor/backend.service.spec.ts @@ -1,8 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; import { BackendService } from './backend.service'; -import {HttpClientModule} from "@angular/common/http"; - describe('HttpClient testing', () => { beforeEach(() => { diff --git a/src/app/workspace-monitor/backend.service.ts b/src/app/workspace-monitor/backend.service.ts index ac1d1bf6..faedd52f 100644 --- a/src/app/workspace-monitor/backend.service.ts +++ b/src/app/workspace-monitor/backend.service.ts @@ -1,9 +1,10 @@ -import { BookletsStarted, MonitorData } from './workspace-monitor.interfaces'; -import {Injectable, Inject} from '@angular/core'; +import { Injectable, Inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { catchError } from 'rxjs/operators'; + import { ErrorHandler, ServerError } from 'iqb-components'; +import { BookletsStarted, MonitorData } from './workspace-monitor.interfaces'; @Injectable({ providedIn: 'root' @@ -16,28 +17,25 @@ export class BackendService { constructor( @Inject('SERVER_URL') private readonly serverUrl: string, private http: HttpClient) { - this.serverUrlSlim = this.serverUrl + 'php/ws.php/'; this.serverUrlSysCheck = this.serverUrl + 'php_admin/'; this.serverUrl = this.serverUrl + 'php/'; } - getBookletsStarted(workspaceId: number, groups: string[]): Observable<BookletsStarted[] | ServerError> { - + getBookletsStarted(workspaceId: number, groups: string[]) + : Observable<BookletsStarted[] | ServerError> { return this.http .get<BookletsStarted[]>(this.serverUrl + `workspace/${workspaceId}/booklets/started`, {params: {groups: groups.join(',')}}) .pipe(catchError(ErrorHandler.handle)); } lockBooklets(workspaceId: number, groups: string[]): Observable<boolean | ServerError> { - return this.http .patch<boolean>(this.serverUrl + `workspace/${workspaceId}/tests/lock`, {groups: groups}) .pipe(catchError(ErrorHandler.handle)); } unlockBooklets(workspaceId: number, groups: string[]): Observable<boolean | ServerError> { - return this.http .patch<boolean>(this.serverUrl + `workspace/${workspaceId}/tests/unlock`, {groups: groups}) .pipe(catchError(ErrorHandler.handle)); @@ -45,7 +43,6 @@ export class BackendService { getMonitorData(workspaceId: number): Observable<MonitorData[] | ServerError> { - return this.http .get<MonitorData[]>(this.serverUrl + `workspace/${workspaceId}/status`, {}) .pipe(catchError(ErrorHandler.handle)); diff --git a/src/app/workspace-monitor/workspace-monitor-routing.module.ts b/src/app/workspace-monitor/workspace-monitor-routing.module.ts index 1545e919..0ec6b9f9 100644 --- a/src/app/workspace-monitor/workspace-monitor-routing.module.ts +++ b/src/app/workspace-monitor/workspace-monitor-routing.module.ts @@ -1,9 +1,8 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import {WorkspaceMonitorComponent} from "./workspace-monitor.component"; +import { WorkspaceMonitorComponent } from './workspace-monitor.component'; - -const routes: Routes = [ { +const routes: Routes = [{ path: '', component: WorkspaceMonitorComponent }]; diff --git a/src/app/workspace-monitor/workspace-monitor.component.ts b/src/app/workspace-monitor/workspace-monitor.component.ts index a24039f6..8a43a5f3 100644 --- a/src/app/workspace-monitor/workspace-monitor.component.ts +++ b/src/app/workspace-monitor/workspace-monitor.component.ts @@ -1,14 +1,17 @@ -import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {MatTableDataSource} from "@angular/material/table"; -import {SelectionModel} from "@angular/cdk/collections"; -import {BookletsStarted, MonitorData} from "./workspace-monitor.interfaces"; -import {Subscription} from "rxjs"; -import {MatSort} from "@angular/material/sort"; -import {BackendService} from "./backend.service"; -import {MatSnackBar} from "@angular/material/snack-bar"; -import {MainDataService} from "../maindata.service"; -import {ActivatedRoute} from "@angular/router"; -import {ServerError} from "iqb-components"; +import { + Component, OnDestroy, OnInit, ViewChild +} from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { SelectionModel } from '@angular/cdk/collections'; +import { ActivatedRoute } from '@angular/router'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { Subscription } from 'rxjs'; + +import { ServerError } from 'iqb-components'; +import { BookletsStarted, MonitorData } from './workspace-monitor.interfaces'; +import { BackendService } from './backend.service'; +import { MainDataService } from '../maindata.service'; @Component({ selector: 'app-workspace-monitor', @@ -34,15 +37,15 @@ export class WorkspaceMonitorComponent implements OnInit, OnDestroy { public snackBar: MatSnackBar ) { } - ngOnInit() { - this.routingSubscription = this.route.params.subscribe(params => { + ngOnInit(): void { + this.routingSubscription = this.route.params.subscribe((params) => { this.workspaceId = Number(params['ws']); this.workspaceName = 'xx'; // TODO where to get the workspace name from? this.updateTable(); }); } - updateTable() { + updateTable(): void { this.dataLoading = true; this.tableselectionCheckbox.clear(); this.bs.getMonitorData(this.workspaceId).subscribe( @@ -54,13 +57,13 @@ export class WorkspaceMonitorComponent implements OnInit, OnDestroy { ); } - isAllSelected() { + isAllSelected(): boolean { const numSelected = this.tableselectionCheckbox.selected.length; const numRows = this.monitorDataSource.data.length; return numSelected === numRows; } - masterToggle() { + masterToggle(): void { this.isAllSelected() ? this.tableselectionCheckbox.clear() : this.monitorDataSource.data.forEach(row => this.tableselectionCheckbox.select(row)); @@ -73,53 +76,51 @@ export class WorkspaceMonitorComponent implements OnInit, OnDestroy { this.tableselectionCheckbox.selected.forEach(element => { selectedGroups.push(element.groupname); }); - this.bs.getBookletsStarted(this.workspaceId, selectedGroups).subscribe(bData => { - - const bookletList = bData as BookletsStarted[]; - if (bookletList.length > 0) { - const columnDelimiter = ';'; - const lineDelimiter = '\n'; - - let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + - 'bookletname' + columnDelimiter + 'locked' + columnDelimiter + 'laststart' + lineDelimiter; - bookletList.forEach((b: BookletsStarted) => { - myCsvData += '"' - + b.groupname + '"' + columnDelimiter + '"' - + b.loginname + '"' + columnDelimiter + '"' - + b.code + '"' + columnDelimiter + '"' - + b.bookletname + '"' + columnDelimiter - + '"' + (b.locked ? 'X' : '-') + '"' + columnDelimiter + '"' - + b.laststart + '"' + lineDelimiter; - }); - const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); - saveAs(blob, 'iqb-testcenter-bookletsStarted.csv'); - } else { - this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); - } + this.bs.getBookletsStarted(this.workspaceId, selectedGroups).subscribe((bData) => { + const bookletList = bData as BookletsStarted[]; + if (bookletList.length > 0) { + const columnDelimiter = ';'; + const lineDelimiter = '\n'; - this.tableselectionCheckbox.clear(); - this.dataLoading = false; + let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + + 'bookletname' + columnDelimiter + 'locked' + columnDelimiter + 'laststart' + lineDelimiter; + bookletList.forEach((b: BookletsStarted) => { + myCsvData += '"' + + b.groupname + '"' + columnDelimiter + '"' + + b.loginname + '"' + columnDelimiter + '"' + + b.code + '"' + columnDelimiter + '"' + + b.bookletname + '"' + columnDelimiter + + '"' + (b.locked ? 'X' : '-') + '"' + columnDelimiter + '"' + + b.laststart + '"' + lineDelimiter; + }); + const blob = new Blob([myCsvData], { type: 'text/csv;charset=utf-8' }); + saveAs(blob, 'iqb-testcenter-bookletsStarted.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', { duration: 3000 }); } - ); + + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); } } - lock() { + lock(): void { if (this.tableselectionCheckbox.selected.length > 0) { this.dataLoading = true; const selectedGroups: string[] = []; - this.tableselectionCheckbox.selected.forEach(element => { + this.tableselectionCheckbox.selected.forEach((element) => { selectedGroups.push(element.groupname); }); - this.bs.lockBooklets(this.workspaceId, selectedGroups).subscribe(success => { + this.bs.lockBooklets(this.workspaceId, selectedGroups).subscribe((success) => { if (success instanceof ServerError) { - this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Systemfehler', {duration: 3000}); + this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Systemfehler', { duration: 3000 }); } else { const ok = success as boolean; if (ok) { - this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', {duration: 1000}); + this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', { duration: 1000 }); } else { - this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', {duration: 3000}); + this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', { duration: 3000 }); } } this.dataLoading = false; @@ -128,22 +129,22 @@ export class WorkspaceMonitorComponent implements OnInit, OnDestroy { } } - unlock() { + unlock(): void { if (this.tableselectionCheckbox.selected.length > 0) { this.dataLoading = true; const selectedGroups: string[] = []; - this.tableselectionCheckbox.selected.forEach(element => { + this.tableselectionCheckbox.selected.forEach((element) => { selectedGroups.push(element.groupname); }); - this.bs.unlockBooklets(this.workspaceId, selectedGroups).subscribe(success => { + this.bs.unlockBooklets(this.workspaceId, selectedGroups).subscribe((success) => { if (success instanceof ServerError) { - this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Systemfehler', {duration: 3000}); + this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Systemfehler', { duration: 3000 }); } else { const ok = success as boolean; if (ok) { - this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', {duration: 1000}); + this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', { duration: 1000 }); } else { - this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', {duration: 3000}); + this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', { duration: 3000 }); } } this.dataLoading = false; @@ -152,8 +153,7 @@ export class WorkspaceMonitorComponent implements OnInit, OnDestroy { } } - // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % - ngOnDestroy() { + ngOnDestroy(): void { if (this.routingSubscription !== null) { this.routingSubscription.unsubscribe(); } diff --git a/src/app/workspace-monitor/workspace-monitor.module.spec.ts b/src/app/workspace-monitor/workspace-monitor.module.spec.ts index 0ce77493..4118ff7f 100644 --- a/src/app/workspace-monitor/workspace-monitor.module.spec.ts +++ b/src/app/workspace-monitor/workspace-monitor.module.spec.ts @@ -1,4 +1,4 @@ -import {WorkspaceMonitorModule} from "./workspace-monitor.module"; +import { WorkspaceMonitorModule } from './workspace-monitor.module'; describe('WorkspaceMonitorModule', () => { let workspaceMonitorModule: WorkspaceMonitorModule; diff --git a/src/app/workspace-monitor/workspace-monitor.module.ts b/src/app/workspace-monitor/workspace-monitor.module.ts index 0ae150d0..0fc765bd 100644 --- a/src/app/workspace-monitor/workspace-monitor.module.ts +++ b/src/app/workspace-monitor/workspace-monitor.module.ts @@ -1,21 +1,20 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { MatTableModule } from '@angular/material/table'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { MatIconModule } from '@angular/material/icon'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { IqbComponentsModule } from 'iqb-components'; +import { BackendService } from './backend.service'; import { WorkspaceMonitorRoutingModule } from './workspace-monitor-routing.module'; import { WorkspaceMonitorComponent } from './workspace-monitor.component'; -import {MatTableModule} from "@angular/material/table"; -import {MatSortModule} from "@angular/material/sort"; -import {MatSnackBarModule} from "@angular/material/snack-bar"; -import {IqbComponentsModule} from "iqb-components"; -import {FlexLayoutModule} from "@angular/flex-layout"; -import {MatIconModule} from "@angular/material/icon"; -import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; -import {MatTooltipModule} from "@angular/material/tooltip"; -import {MatCheckboxModule} from "@angular/material/checkbox"; -import {ReactiveFormsModule} from "@angular/forms"; -import {MatButtonModule} from "@angular/material/button"; -import {BackendService} from "./backend.service"; - @NgModule({ declarations: [WorkspaceMonitorComponent], diff --git a/src/main.ts b/src/main.ts index 6d56526f..e0bf3a6a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import {enableProdMode, StaticProvider} from '@angular/core'; +import { enableProdMode, StaticProvider } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; @@ -34,7 +34,7 @@ platformBrowserDynamic(<StaticProvider[]>[ provide: 'VERONA_API_VERSION_SUPPORTED', useValue: environment.veronaApiVersionSupported }, -{ + { provide: 'REPOSITORY_URL', useValue: repository.url }, @@ -43,4 +43,4 @@ platformBrowserDynamic(<StaticProvider[]>[ useValue: environment.production } ]).bootstrapModule(AppModule) - .catch(err => console.log(err)); + .catch((err) => console.log(err)); diff --git a/src/scripts/findCustomTexts.js b/src/scripts/findCustomTexts.js index 81814974..e63efb6c 100644 --- a/src/scripts/findCustomTexts.js +++ b/src/scripts/findCustomTexts.js @@ -2,6 +2,7 @@ // listing of all used keys for customText const fs = require("fs"); + const definitionFilename = '../app/config/custom-texts.json'; const startFolder = '../app'; const mdSourceFilename = '../app/config/custom-texts.md'; diff --git a/src/scripts/generateBookletConfigClass.js b/src/scripts/generateBookletConfigClass.js index 105216d5..cc6bf2a9 100644 --- a/src/scripts/generateBookletConfigClass.js +++ b/src/scripts/generateBookletConfigClass.js @@ -1,6 +1,7 @@ // generates booklet-config.ts as class with all data from JSON: const fs = require("fs"); + const definitionFilename = '../app/config/booklet-config.json'; const targetTsFilename = '../app/config/booklet-config.ts'; const mdSourceFilename = '../app/config/booklet-config.md'; diff --git a/src/scripts/generateTestModeClass.js b/src/scripts/generateTestModeClass.js index 6a6e5d09..33a80103 100644 --- a/src/scripts/generateTestModeClass.js +++ b/src/scripts/generateTestModeClass.js @@ -3,6 +3,7 @@ // TODO saveResponses should be renamed, since it affects more than only response saving (like logging etc. pp.) const fs = require("fs"); + const definitionOptionsFilename = '../app/config/mode-options.json'; const definitionModesFilename = '../app/config/test-modes.json'; const targetTsFilename = '../app/config/test-mode.ts'; @@ -15,11 +16,11 @@ console.log('writing TypeScript'); const definitionOptions = JSON.parse(fs.readFileSync(definitionOptionsFilename)); const definitionModes = JSON.parse(fs.readFileSync(definitionModesFilename)); -let fileContent = "// @ts-ignore\n"; +let fileContent = '// @ts-ignore\n'; fileContent += "import testModes from './test-modes.json';\n\n"; fileContent += "// this file is generated by 'generateTestModeClass' script from 'app/config/test-modes.json' and 'app/config/mode-options.json'\n"; -fileContent += "// do not change anything here directly!\n\n"; -fileContent += "export class TestMode {\n"; +fileContent += '// do not change anything here directly!\n\n'; +fileContent += 'export class TestMode {\n'; const demoConfig = definitionModes.DEMO.config; @@ -38,27 +39,27 @@ fileContent += "\t\t\t\tconst modeConfig = testModes[mode];\n"; for (const k of Object.keys(definitionOptions)) { fileContent += "\t\t\t\tthis." + k + " = modeConfig.config." + k + ";\n"; } -fileContent += "\t\t\t\tthis.modeLabel = modeConfig.label;\n"; -fileContent += "\t\t\t\tthis.modeId = mode;\n"; +fileContent += '\t\t\t\tthis.modeLabel = modeConfig.label;\n'; +fileContent += '\t\t\t\tthis.modeId = mode;\n'; -fileContent += "\t\t\t} else {\n"; +fileContent += '\t\t\t} else {\n'; fileContent += "\t\t\t\tconsole.error('TestConfig: invalid loginMode - take DEMO');\n"; -fileContent += "\t\t\t}\n"; -fileContent += "\t\t} else {\n"; +fileContent += '\t\t\t}\n'; +fileContent += '\t\t} else {\n'; fileContent += "\t\t\tconsole.error('TestConfig: empty loginMode - take DEMO');\n"; -fileContent += "\t\t}\n\t}\n"; +fileContent += '\t\t}\n\t}\n'; -fileContent += "}\n"; +fileContent += '}\n'; -fs.writeFileSync(targetTsFilename, fileContent, "utf8"); +fs.writeFileSync(targetTsFilename, fileContent, 'utf8'); console.log(''); console.log('writing markdown'); let mdContent = fs.readFileSync(mdSourceFilename, 'utf8').toString(); -let tableHeader1 = "| | "; -let tableHeader2 = "| :------------- |"; +let tableHeader1 = '| | '; +let tableHeader2 = '| :------------- |'; for (const k of Object.keys(definitionModes)) { mdContent += '* `' + k + (k === 'DEMO' ? '` (default): ' : '`: ') + definitionModes[k].label + '\n'; tableHeader1 += '`' + k + '` | '; @@ -74,7 +75,7 @@ for (const ok of Object.keys(definitionOptions)) { } mdContent += '\n'; } -fs.writeFileSync(mdTargetFilename, mdContent, "utf8"); +fs.writeFileSync(mdTargetFilename, mdContent, 'utf8'); console.log(''); console.log('done.'); diff --git a/src/test.ts b/src/test.ts index 6eabcd9f..b1840735 100644 --- a/src/test.ts +++ b/src/test.ts @@ -6,8 +6,8 @@ import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import {StaticProvider} from "@angular/core"; -import {environment} from "./environments/environment"; +import { StaticProvider } from '@angular/core'; +import { environment } from './environments/environment'; import { name, version, repository } from '../package.json'; declare const require: any; -- GitLab