From 2c424556895df8a160dae69123c764d287fe39a7 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Fri, 14 Aug 2020 09:15:50 -0400 Subject: [PATCH] Added the clear task audit log feature [#424] * Added a new state to the backend-status module. * Added a toolbar to the audit log list page to clear the log. --- .../actions/clear-task-audit-log.actions.ts | 31 +++ .../actions/task-audit-log.actions.ts | 28 +-- .../backend-status.constants.ts | 1 + .../backend-status/backend-status.module.ts | 18 +- .../clear-task-audit-log.effects.spec.ts | 125 +++++++++++ .../effects/clear-task-audit-log.effects.ts | 94 ++++++++ .../effects/task-audit-log.effects.spec.ts | 208 +++++++++--------- .../src/app/backend-status/index.ts | 8 +- .../task-audit-log-page.component.html | 11 +- .../task-audit-log-page.component.spec.ts | 60 ++++- .../task-audit-log-page.component.ts | 25 ++- .../clear-task-audit-log.reducer.spec.ts | 76 +++++++ .../reducers/clear-task-audit-log.reducer.ts | 49 +++++ .../reducers/task-audit-log.reducer.spec.ts | 50 +---- .../reducers/task-audit-log.reducer.ts | 13 +- .../clear-task-audit-log.selectors.spec.ts | 50 +++++ .../clear-task-audit-log.selectors.ts | 36 +++ .../services/task-audit-log.service.spec.ts | 19 +- .../services/task-audit-log.service.ts | 18 +- comixed-frontend/src/app/core/core.module.ts | 4 +- comixed-frontend/src/app/core/index.ts | 1 + .../app/core/services/alert.service.spec.ts | 30 +++ .../src/app/core/services/alert.service.ts | 33 +++ .../src/assets/i18n/en/backend-status.json | 17 ++ .../src/assets/i18n/es/backend-status.json | 17 ++ .../src/assets/i18n/fr/backend-status.json | 17 ++ .../src/assets/i18n/pt/backend-status.json | 17 ++ 27 files changed, 843 insertions(+), 213 deletions(-) create mode 100644 comixed-frontend/src/app/backend-status/actions/clear-task-audit-log.actions.ts create mode 100644 comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.spec.ts create mode 100644 comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.ts create mode 100644 comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.spec.ts create mode 100644 comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.ts create mode 100644 comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.spec.ts create mode 100644 comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.ts create mode 100644 comixed-frontend/src/app/core/services/alert.service.spec.ts create mode 100644 comixed-frontend/src/app/core/services/alert.service.ts diff --git a/comixed-frontend/src/app/backend-status/actions/clear-task-audit-log.actions.ts b/comixed-frontend/src/app/backend-status/actions/clear-task-audit-log.actions.ts new file mode 100644 index 000000000..d8a081206 --- /dev/null +++ b/comixed-frontend/src/app/backend-status/actions/clear-task-audit-log.actions.ts @@ -0,0 +1,31 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { createAction } from '@ngrx/store'; + +export const clearTaskAuditLog = createAction( + '[ClearTaskAuditLog] Clear the log' +); + +export const taskAuditLogCleared = createAction( + '[ClearTaskAuditLog] The log was cleared' +); + +export const cleartaskAuditLogFailed = createAction( + '[ClearTaskAuditLog] Failed to clear the log' +); diff --git a/comixed-frontend/src/app/backend-status/actions/task-audit-log.actions.ts b/comixed-frontend/src/app/backend-status/actions/task-audit-log.actions.ts index cb0dea050..20361a367 100644 --- a/comixed-frontend/src/app/backend-status/actions/task-audit-log.actions.ts +++ b/comixed-frontend/src/app/backend-status/actions/task-audit-log.actions.ts @@ -22,10 +22,7 @@ import { TaskAuditLogEntry } from 'app/backend-status/models/task-audit-log-entr export enum TaskAuditLogActionTypes { GetEntries = '[TASK AUDIT LOG] Get task audit log entries', ReceivedEntries = '[TASK AUDIT LOG] Task audit log entries received', - GetEntriesFailed = '[TASK AUDIT LOG] Failed to get task audit log entries', - ClearLog = '[TASK AUDIT LOG] Clear the audit log', - LogCleared = '[TASK AUDIT LOG] The log is cleared', - ClearLogFailed = '[TASK AUDIT LOG] Failed to clear the audit log' + GetEntriesFailed = '[TASK AUDIT LOG] Failed to get task audit log entries' } export class GetTaskAuditLogEntries implements Action { @@ -50,28 +47,7 @@ export class GetTaskAuditLogEntriesFailed implements Action { constructor() {} } -export class ClearTaskAuditLog implements Action { - readonly type = TaskAuditLogActionTypes.ClearLog; - - constructor() {} -} - -export class TaskAuditLogCleared implements Action { - readonly type = TaskAuditLogActionTypes.LogCleared; - - constructor() {} -} - -export class ClearTaskAuditLogFailed implements Action { - readonly type = TaskAuditLogActionTypes.ClearLogFailed; - - constructor() {} -} - export type TaskAuditLogActions = | GetTaskAuditLogEntries | ReceivedTaskAuditLogEntries - | GetTaskAuditLogEntriesFailed - | ClearTaskAuditLog - | TaskAuditLogCleared - | ClearTaskAuditLogFailed; + | GetTaskAuditLogEntriesFailed; diff --git a/comixed-frontend/src/app/backend-status/backend-status.constants.ts b/comixed-frontend/src/app/backend-status/backend-status.constants.ts index 69b47f2b4..12abd5b97 100644 --- a/comixed-frontend/src/app/backend-status/backend-status.constants.ts +++ b/comixed-frontend/src/app/backend-status/backend-status.constants.ts @@ -19,3 +19,4 @@ import { API_ROOT_URL } from 'app/app.functions'; export const GET_TASK_LOG_ENTRIES_URL = `${API_ROOT_URL}/tasks/entries/\${timestamp}`; +export const CLEAR_TASK_AUDIT_LOG_URL = `${API_ROOT_URL}/tasks/entries`; diff --git a/comixed-frontend/src/app/backend-status/backend-status.module.ts b/comixed-frontend/src/app/backend-status/backend-status.module.ts index 2dd49d2e6..c3be11771 100644 --- a/comixed-frontend/src/app/backend-status/backend-status.module.ts +++ b/comixed-frontend/src/app/backend-status/backend-status.module.ts @@ -25,6 +25,7 @@ import { BuildDetailsAdaptor } from 'app/backend-status/adaptors/build-details.a import { StoreModule } from '@ngrx/store'; import * as fromBuildDetails from './reducers/build-details.reducer'; import * as fromTaskAuditLog from './reducers/task-audit-log.reducer'; +import * as fromClearTaskAuditLog from './reducers/clear-task-audit-log.reducer'; import { EffectsModule } from '@ngrx/effects'; import { BuildDetailsEffects } from 'app/backend-status/effects/build-details.effects'; import { TranslateModule } from '@ngx-translate/core'; @@ -33,6 +34,9 @@ import { TaskAuditLogPageComponent } from './pages/task-audit-log-page/task-audi import { TaskAuditLogAdaptor } from 'app/backend-status/adaptors/task-audit-log.adaptor'; import { TableModule } from 'primeng/table'; import { CoreModule } from 'app/core/core.module'; +import { CLEAR_TASK_AUDIT_LOG_FEATURE_KEY } from 'app/backend-status/reducers/clear-task-audit-log.reducer'; +import { ClearTaskAuditLogEffects } from 'app/backend-status/effects/clear-task-audit-log.effects'; +import { ToolbarModule, TooltipModule } from 'primeng/primeng'; @NgModule({ declarations: [BuildDetailsPageComponent, TaskAuditLogPageComponent], @@ -49,8 +53,18 @@ import { CoreModule } from 'app/core/core.module'; fromTaskAuditLog.TASK_AUDIT_LOG_FEATURE_KEY, fromTaskAuditLog.reducer ), - EffectsModule.forFeature([BuildDetailsEffects, TaskAuditLogEffects]), - TableModule + StoreModule.forFeature( + CLEAR_TASK_AUDIT_LOG_FEATURE_KEY, + fromClearTaskAuditLog.reducer + ), + EffectsModule.forFeature([ + BuildDetailsEffects, + TaskAuditLogEffects, + ClearTaskAuditLogEffects + ]), + TableModule, + ToolbarModule, + TooltipModule ], exports: [CommonModule, CoreModule], providers: [BuildDetailsService, BuildDetailsAdaptor, TaskAuditLogAdaptor] diff --git a/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.spec.ts b/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.spec.ts new file mode 100644 index 000000000..e113b0c39 --- /dev/null +++ b/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.spec.ts @@ -0,0 +1,125 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Observable, of, throwError } from 'rxjs'; + +import { ClearTaskAuditLogEffects } from './clear-task-audit-log.effects'; +import { TaskAuditLogService } from 'app/backend-status/services/task-audit-log.service'; +import { AlertService, ApiResponse } from 'app/core'; +import { CoreModule } from 'app/core/core.module'; +import { + clearTaskAuditLog, + cleartaskAuditLogFailed, + taskAuditLogCleared +} from 'app/backend-status/actions/clear-task-audit-log.actions'; +import { hot } from 'jasmine-marbles'; +import { LoggerModule } from '@angular-ru/logger'; +import { HttpErrorResponse } from '@angular/common/http'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('ClearTaskAuditLogEffects', () => { + let actions$: Observable; + let effects: ClearTaskAuditLogEffects; + let taskAuditLogService: jasmine.SpyObj; + let alertService: AlertService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CoreModule, LoggerModule.forRoot(), TranslateModule.forRoot()], + providers: [ + ClearTaskAuditLogEffects, + provideMockActions(() => actions$), + { + provide: TaskAuditLogService, + useValue: { + clearAuditLog: jasmine.createSpy( + 'TaskAuditLogService.clearAuditLog()' + ) + } + } + ] + }); + + effects = TestBed.get(ClearTaskAuditLogEffects); + taskAuditLogService = TestBed.get(TaskAuditLogService); + alertService = TestBed.get(AlertService); + spyOn(alertService, 'info'); + spyOn(alertService, 'error'); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('clearing the audit log', () => { + it('fires an action on success', () => { + const serviceResponse = { success: true } as ApiResponse; + const action = clearTaskAuditLog(); + const outcome = taskAuditLogCleared(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.clearAuditLog.and.returnValue(of(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.clearTaskAuditLog$).toBeObservable(expected); + expect(alertService.info).toHaveBeenCalledWith(jasmine.any(String)); + }); + + it('fires an action on failure', () => { + const serviceResponse = { success: false } as ApiResponse; + const action = clearTaskAuditLog(); + const outcome = cleartaskAuditLogFailed(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.clearAuditLog.and.returnValue(of(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.clearTaskAuditLog$).toBeObservable(expected); + expect(alertService.error).toHaveBeenCalledWith(jasmine.any(String)); + }); + + it('fires an action on service failure', () => { + const serviceResponse = new HttpErrorResponse({}); + const action = clearTaskAuditLog(); + const outcome = cleartaskAuditLogFailed(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.clearAuditLog.and.returnValue( + throwError(serviceResponse) + ); + + const expected = hot('-b', { b: outcome }); + expect(effects.clearTaskAuditLog$).toBeObservable(expected); + expect(alertService.error).toHaveBeenCalledWith(jasmine.any(String)); + }); + + it('fires an action on general failure', () => { + const action = clearTaskAuditLog(); + const outcome = cleartaskAuditLogFailed(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.clearAuditLog.and.throwError('expected'); + + const expected = hot('-(b|)', { b: outcome }); + expect(effects.clearTaskAuditLog$).toBeObservable(expected); + expect(alertService.error).toHaveBeenCalledWith(jasmine.any(String)); + }); + }); +}); diff --git a/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.ts b/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.ts new file mode 100644 index 000000000..14992552e --- /dev/null +++ b/comixed-frontend/src/app/backend-status/effects/clear-task-audit-log.effects.ts @@ -0,0 +1,94 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { + clearTaskAuditLog, + cleartaskAuditLogFailed, + taskAuditLogCleared +} from 'app/backend-status/actions/clear-task-audit-log.actions'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; +import { LoggerService } from '@angular-ru/logger'; +import { TaskAuditLogService } from 'app/backend-status/services/task-audit-log.service'; +import { AlertService, ApiResponse } from 'app/core'; +import { TranslateService } from '@ngx-translate/core'; +import { of } from 'rxjs'; + +@Injectable() +export class ClearTaskAuditLogEffects { + constructor( + private logger: LoggerService, + private actions$: Actions, + private taskAuditLogService: TaskAuditLogService, + private alertService: AlertService, + private translateService: TranslateService + ) {} + + clearTaskAuditLog$ = createEffect(() => { + return this.actions$.pipe( + ofType(clearTaskAuditLog), + tap(() => this.logger.debug('effect: clear audit log')), + switchMap(() => + this.taskAuditLogService.clearAuditLog().pipe( + tap(response => this.logger.debug('received response:', response)), + tap((response: ApiResponse) => + response.success + ? this.alertService.info( + this.translateService.instant( + 'tasks.audit-log.effects.clear-audit-log.success.detail' + ) + ) + : this.alertService.error( + this.translateService.instant( + 'tasks.audit-log.effects.clear-audit-log.success.detail' + ) + ) + ), + map((response: ApiResponse) => + response.success ? taskAuditLogCleared() : cleartaskAuditLogFailed() + ), + catchError(error => { + this.logger.error( + 'service failure clearing the task audit log:', + error + ); + this.alertService.error( + this.translateService.instant( + 'tasks.audit-log.effects.clear-audit-log.success.detail' + ) + ); + return of(cleartaskAuditLogFailed()); + }) + ) + ), + catchError(error => { + this.logger.error( + 'service failure clearing the task audit log:', + error + ); + this.alertService.error( + this.translateService.instant( + 'general-message.error.general-service-failure' + ) + ); + return of(cleartaskAuditLogFailed()); + }) + ); + }); +} diff --git a/comixed-frontend/src/app/backend-status/effects/task-audit-log.effects.spec.ts b/comixed-frontend/src/app/backend-status/effects/task-audit-log.effects.spec.ts index 7771f633a..44ea8e8e2 100644 --- a/comixed-frontend/src/app/backend-status/effects/task-audit-log.effects.spec.ts +++ b/comixed-frontend/src/app/backend-status/effects/task-audit-log.effects.spec.ts @@ -16,121 +16,121 @@ * along with this program. If not, see */ -import {TestBed} from '@angular/core/testing'; -import {provideMockActions} from '@ngrx/effects/testing'; -import {Observable, of, throwError} from 'rxjs'; +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Observable, of, throwError } from 'rxjs'; -import {TaskAuditLogEffects} from './task-audit-log.effects'; -import {TaskAuditLogService} from 'app/backend-status/services/task-audit-log.service'; +import { TaskAuditLogEffects } from './task-audit-log.effects'; +import { TaskAuditLogService } from 'app/backend-status/services/task-audit-log.service'; import { - TASK_AUDIT_LOG_ENTRY_1, - TASK_AUDIT_LOG_ENTRY_2, - TASK_AUDIT_LOG_ENTRY_3, - TASK_AUDIT_LOG_ENTRY_4, - TASK_AUDIT_LOG_ENTRY_5 + TASK_AUDIT_LOG_ENTRY_1, + TASK_AUDIT_LOG_ENTRY_2, + TASK_AUDIT_LOG_ENTRY_3, + TASK_AUDIT_LOG_ENTRY_4, + TASK_AUDIT_LOG_ENTRY_5 } from 'app/backend-status/backend-status.fixtures'; import { - GetTaskAuditLogEntries, - GetTaskAuditLogEntriesFailed, - ReceivedTaskAuditLogEntries + GetTaskAuditLogEntries, + GetTaskAuditLogEntriesFailed, + ReceivedTaskAuditLogEntries } from 'app/backend-status/actions/task-audit-log.actions'; -import {hot} from 'jasmine-marbles'; -import {HttpErrorResponse} from '@angular/common/http'; -import {MessageService} from 'primeng/api'; -import {LoggerModule} from '@angular-ru/logger'; -import {TranslateModule} from '@ngx-translate/core'; +import { hot } from 'jasmine-marbles'; +import { HttpErrorResponse } from '@angular/common/http'; +import { MessageService } from 'primeng/api'; +import { LoggerModule } from '@angular-ru/logger'; +import { TranslateModule } from '@ngx-translate/core'; import objectContaining = jasmine.objectContaining; describe('TaskAuditLogEffects', () => { - const LOG_ENTRIES = [ - TASK_AUDIT_LOG_ENTRY_1, - TASK_AUDIT_LOG_ENTRY_2, - TASK_AUDIT_LOG_ENTRY_3, - TASK_AUDIT_LOG_ENTRY_4, - TASK_AUDIT_LOG_ENTRY_5 - ]; - - let actions$: Observable; - let effects: TaskAuditLogEffects; - let taskAuditLogService: jasmine.SpyObj; - let messageService: MessageService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [LoggerModule.forRoot(), TranslateModule.forRoot()], - providers: [ - TaskAuditLogEffects, - provideMockActions(() => actions$), - { - provide: TaskAuditLogService, - useValue: { - getLogEntries: jasmine.createSpy( - 'TaskAuditLogService.getLogEntries()' - ) - } - }, - MessageService - ] - }); - - effects = TestBed.get(TaskAuditLogEffects); - taskAuditLogService = TestBed.get(TaskAuditLogService); - messageService = TestBed.get(MessageService); - spyOn(messageService, 'add'); + const LOG_ENTRIES = [ + TASK_AUDIT_LOG_ENTRY_1, + TASK_AUDIT_LOG_ENTRY_2, + TASK_AUDIT_LOG_ENTRY_3, + TASK_AUDIT_LOG_ENTRY_4, + TASK_AUDIT_LOG_ENTRY_5 + ]; + + let actions$: Observable; + let effects: TaskAuditLogEffects; + let taskAuditLogService: jasmine.SpyObj; + let messageService: MessageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [LoggerModule.forRoot(), TranslateModule.forRoot()], + providers: [ + TaskAuditLogEffects, + provideMockActions(() => actions$), + { + provide: TaskAuditLogService, + useValue: { + getLogEntries: jasmine.createSpy( + 'TaskAuditLogService.getLogEntries()' + ) + } + }, + MessageService + ] }); - it('should be created', () => { - expect(effects).toBeTruthy(); + effects = TestBed.get(TaskAuditLogEffects); + taskAuditLogService = TestBed.get(TaskAuditLogService); + messageService = TestBed.get(MessageService); + spyOn(messageService, 'add'); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('getting the list of log entries', () => { + it('fires an action on success', () => { + const serviceResponse = LOG_ENTRIES; + const action = new GetTaskAuditLogEntries({ + cutoff: new Date().getTime() + }); + const outcome = new ReceivedTaskAuditLogEntries({ entries: LOG_ENTRIES }); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.getLogEntries.and.returnValue(of(serviceResponse)); + + const expected = hot('-b', { b: outcome }); + expect(effects.getLogEntries$).toBeObservable(expected); }); - describe('getting the list of log entries', () => { - it('fires an action on success', () => { - const serviceResponse = LOG_ENTRIES; - const action = new GetTaskAuditLogEntries({ - cutoff: new Date().getTime() - }); - const outcome = new ReceivedTaskAuditLogEntries({entries: LOG_ENTRIES}); - - actions$ = hot('-a', {a: action}); - taskAuditLogService.getLogEntries.and.returnValue(of(serviceResponse)); - - const expected = hot('-b', {b: outcome}); - expect(effects.getLogEntries$).toBeObservable(expected); - }); - - it('fires an action on service failure', () => { - const serviceResponse = new HttpErrorResponse({}); - const action = new GetTaskAuditLogEntries({ - cutoff: new Date().getTime() - }); - const outcome = new GetTaskAuditLogEntriesFailed(); - - actions$ = hot('-a', {a: action}); - taskAuditLogService.getLogEntries.and.returnValue( - throwError(serviceResponse) - ); - - const expected = hot('-b', {b: outcome}); - expect(effects.getLogEntries$).toBeObservable(expected); - expect(messageService.add).toHaveBeenCalledWith( - objectContaining({severity: 'error'}) - ); - }); - - it('fires an action on general failure', () => { - const action = new GetTaskAuditLogEntries({ - cutoff: new Date().getTime() - }); - const outcome = new GetTaskAuditLogEntriesFailed(); - - actions$ = hot('-a', {a: action}); - taskAuditLogService.getLogEntries.and.throwError('expected'); - - const expected = hot('-(b|)', {b: outcome}); - expect(effects.getLogEntries$).toBeObservable(expected); - expect(messageService.add).toHaveBeenCalledWith( - objectContaining({severity: 'error'}) - ); - }); + it('fires an action on service failure', () => { + const serviceResponse = new HttpErrorResponse({}); + const action = new GetTaskAuditLogEntries({ + cutoff: new Date().getTime() + }); + const outcome = new GetTaskAuditLogEntriesFailed(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.getLogEntries.and.returnValue( + throwError(serviceResponse) + ); + + const expected = hot('-b', { b: outcome }); + expect(effects.getLogEntries$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); + }); + + it('fires an action on general failure', () => { + const action = new GetTaskAuditLogEntries({ + cutoff: new Date().getTime() + }); + const outcome = new GetTaskAuditLogEntriesFailed(); + + actions$ = hot('-a', { a: action }); + taskAuditLogService.getLogEntries.and.throwError('expected'); + + const expected = hot('-(b|)', { b: outcome }); + expect(effects.getLogEntries$).toBeObservable(expected); + expect(messageService.add).toHaveBeenCalledWith( + objectContaining({ severity: 'error' }) + ); }); + }); }); diff --git a/comixed-frontend/src/app/backend-status/index.ts b/comixed-frontend/src/app/backend-status/index.ts index 05fd2f32c..054882703 100644 --- a/comixed-frontend/src/app/backend-status/index.ts +++ b/comixed-frontend/src/app/backend-status/index.ts @@ -20,11 +20,13 @@ import * as fromRouter from '@ngrx/router-store'; import * as fromBuildDetails from './reducers/build-details.reducer'; import { BuildDetailsState } from './reducers/build-details.reducer'; import * as fromTaskAuditLog from './reducers/task-audit-log.reducer'; - +import * as fromClearTaskAuditLog from './reducers/clear-task-audit-log.reducer'; import { Params } from '@angular/router'; + import { ActionReducerMap, MetaReducer } from '@ngrx/store'; import { environment } from '../../environments/environment'; import { TaskAuditLogState } from 'app/backend-status/reducers/task-audit-log.reducer'; +import { ClearTaskAuditLogState } from 'app/backend-status/reducers/clear-task-audit-log.reducer'; interface RouterStateUrl { url: string; @@ -36,6 +38,7 @@ export interface AppState { router: fromRouter.RouterReducerState; build_details: BuildDetailsState; task_audit_log_state: TaskAuditLogState; + clear_task_audit_log_state: ClearTaskAuditLogState; } export type State = AppState; @@ -43,7 +46,8 @@ export type State = AppState; export const reducers: ActionReducerMap = { router: fromRouter.routerReducer, build_details: fromBuildDetails.reducer, - task_audit_log_state: fromTaskAuditLog.reducer + task_audit_log_state: fromTaskAuditLog.reducer, + clear_task_audit_log_state: fromClearTaskAuditLog.reducer }; export const metaReducers: MetaReducer[] = !environment.production diff --git a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.html b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.html index d8f82cca7..8c02d238f 100644 --- a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.html +++ b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.html @@ -1,8 +1,17 @@

{{"task-audit-log-page.title"|translate}}

+ +
+ +
+
+ paginatorPosition="both" + [loading]="clearingAuditLog"> diff --git a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.spec.ts b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.spec.ts index b84eecd8e..6b215007b 100644 --- a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.spec.ts +++ b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.spec.ts @@ -22,47 +22,87 @@ import { TaskAuditLogPageComponent } from './task-audit-log-page.component'; import { TableModule } from 'primeng/table'; import { TranslateModule } from '@ngx-translate/core'; import { LoggerModule } from '@angular-ru/logger'; -import { StoreModule } from '@ngrx/store'; -import { - reducer, - TASK_AUDIT_LOG_FEATURE_KEY -} from 'app/backend-status/reducers/task-audit-log.reducer'; +import { Store, StoreModule } from '@ngrx/store'; +import * as fromTaskAuditLog from 'app/backend-status/reducers/task-audit-log.reducer'; +import { TASK_AUDIT_LOG_FEATURE_KEY } from 'app/backend-status/reducers/task-audit-log.reducer'; import { EffectsModule } from '@ngrx/effects'; import { TaskAuditLogEffects } from 'app/backend-status/effects/task-audit-log.effects'; import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { MessageService } from 'primeng/api'; +import { Confirmation, ConfirmationService, MessageService } from 'primeng/api'; import { LibraryModule } from 'app/library/library.module'; import { RouterTestingModule } from '@angular/router/testing'; import { BreadcrumbAdaptor } from 'app/adaptors/breadcrumb.adaptor'; +import { CoreModule } from 'app/core/core.module'; +import { ToolbarModule, TooltipModule } from 'primeng/primeng'; +import { CLEAR_TASK_AUDIT_LOG_FEATURE_KEY } from 'app/backend-status/reducers/clear-task-audit-log.reducer'; +import * as fromClearTaskAuditLog from 'app/backend-status/reducers/build-details.reducer'; +import { ClearTaskAuditLogEffects } from 'app/backend-status/effects/clear-task-audit-log.effects'; +import { AppState } from 'app/backend-status'; +import { clearTaskAuditLog } from 'app/backend-status/actions/clear-task-audit-log.actions'; describe('TaskAuditLogPageComponent', () => { let component: TaskAuditLogPageComponent; let fixture: ComponentFixture; + let store: Store; + let confirmationService: ConfirmationService; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ + CoreModule, LibraryModule, RouterTestingModule, HttpClientTestingModule, TranslateModule.forRoot(), LoggerModule.forRoot(), StoreModule.forRoot({}), - StoreModule.forFeature(TASK_AUDIT_LOG_FEATURE_KEY, reducer), + StoreModule.forFeature( + TASK_AUDIT_LOG_FEATURE_KEY, + fromTaskAuditLog.reducer + ), + StoreModule.forFeature( + CLEAR_TASK_AUDIT_LOG_FEATURE_KEY, + fromClearTaskAuditLog.reducer + ), EffectsModule.forRoot([]), - EffectsModule.forFeature([TaskAuditLogEffects]), - TableModule + EffectsModule.forFeature([ + TaskAuditLogEffects, + ClearTaskAuditLogEffects + ]), + TableModule, + ToolbarModule, + TooltipModule ], declarations: [TaskAuditLogPageComponent], - providers: [MessageService, BreadcrumbAdaptor] + providers: [MessageService, BreadcrumbAdaptor, ConfirmationService] }).compileComponents(); fixture = TestBed.createComponent(TaskAuditLogPageComponent); component = fixture.componentInstance; fixture.detectChanges(); + store = TestBed.get(Store); + spyOn(store, 'dispatch').and.callThrough(); + confirmationService = TestBed.get(ConfirmationService); })); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('clearing the task audit log', () => { + beforeEach(() => { + spyOn(confirmationService, 'confirm').and.callFake( + (confirm: Confirmation) => confirm.accept() + ); + component.doClearAuditLog(); + }); + + it('confirms with the user', () => { + expect(confirmationService.confirm).toHaveBeenCalled(); + }); + + it('notifies the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(clearTaskAuditLog()); + }); + }); }); diff --git a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.ts b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.ts index c13e02783..3aefcbc6f 100644 --- a/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.ts +++ b/comixed-frontend/src/app/backend-status/pages/task-audit-log-page/task-audit-log-page.component.ts @@ -25,6 +25,11 @@ import { LibraryDisplayAdaptor } from 'app/user/adaptors/library-display.adaptor import { BreadcrumbAdaptor } from 'app/adaptors/breadcrumb.adaptor'; import { TranslateService } from '@ngx-translate/core'; import { Title } from '@angular/platform-browser'; +import { ConfirmationService } from 'primeng/api'; +import { Store } from '@ngrx/store'; +import { AppState } from 'app/backend-status'; +import { clearTaskAuditLog } from 'app/backend-status/actions/clear-task-audit-log.actions'; +import { selectClearTaskingAuditLogWorking } from 'app/backend-status/selectors/clear-task-audit-log.selectors'; @Component({ selector: 'app-task-audit-log-page', @@ -39,6 +44,7 @@ export class TaskAuditLogPageComponent implements OnInit, OnDestroy { rowsSubscription: Subscription; rows = 0; langChangeSubscription: Subscription; + clearingAuditLog = false; constructor( private logger: LoggerService, @@ -46,8 +52,13 @@ export class TaskAuditLogPageComponent implements OnInit, OnDestroy { private libraryDisplayAdaptor: LibraryDisplayAdaptor, private breadcrumbAdaptor: BreadcrumbAdaptor, private translateService: TranslateService, - private titleService: Title + private titleService: Title, + private confirmationService: ConfirmationService, + private store: Store ) { + this.store + .select(selectClearTaskingAuditLogWorking) + .subscribe(clearing => (this.clearingAuditLog = clearing)); this.fetchingSubscription = this.taskAuditLogAdaptor.fetchingEntries$.subscribe( fetching => { this.fetching = fetching; @@ -91,4 +102,16 @@ export class TaskAuditLogPageComponent implements OnInit, OnDestroy { } ]); } + + doClearAuditLog() { + this.confirmationService.confirm({ + header: this.translateService.instant( + 'task.clear-audit-log.confirm-header' + ), + message: this.translateService.instant( + 'task.clear-audit-log.confirm-message' + ), + accept: () => this.store.dispatch(clearTaskAuditLog()) + }); + } } diff --git a/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.spec.ts b/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.spec.ts new file mode 100644 index 000000000..5d2b35cb4 --- /dev/null +++ b/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.spec.ts @@ -0,0 +1,76 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { + ClearTaskAuditLogState, + initialState, + reducer +} from './clear-task-audit-log.reducer'; +import { + clearTaskAuditLog, + cleartaskAuditLogFailed, + taskAuditLogCleared +} from 'app/backend-status/actions/clear-task-audit-log.actions'; + +describe('ClearTaskAuditLog Reducer', () => { + let state: ClearTaskAuditLogState; + + beforeEach(() => { + state = initialState; + }); + + describe('the initial state', () => { + beforeEach(() => { + state = reducer({ ...state }, {} as any); + }); + + it('clears the working flag', () => { + expect(state.working).toBeFalsy(); + }); + }); + + describe('clearing the task audit log', () => { + beforeEach(() => { + state = reducer({ ...state, working: false }, clearTaskAuditLog()); + }); + + it('sets the working flag', () => { + expect(state.working).toBeTruthy(); + }); + }); + + describe('clearing the task audit log succeeds', () => { + beforeEach(() => { + state = reducer({ ...state, working: true }, taskAuditLogCleared()); + }); + + it('sets the working flag', () => { + expect(state.working).toBeFalsy(); + }); + }); + + describe('clearing the task audit log fails', () => { + beforeEach(() => { + state = reducer({ ...state, working: true }, cleartaskAuditLogFailed()); + }); + + it('sets the working flag', () => { + expect(state.working).toBeFalsy(); + }); + }); +}); diff --git a/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.ts b/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.ts new file mode 100644 index 000000000..5cd7e05ef --- /dev/null +++ b/comixed-frontend/src/app/backend-status/reducers/clear-task-audit-log.reducer.ts @@ -0,0 +1,49 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Action, createReducer, on } from '@ngrx/store'; +import { + clearTaskAuditLog, + cleartaskAuditLogFailed, + taskAuditLogCleared +} from 'app/backend-status/actions/clear-task-audit-log.actions'; + +export const CLEAR_TASK_AUDIT_LOG_FEATURE_KEY = 'clear_task_audit_log_state'; + +export interface ClearTaskAuditLogState { + working: boolean; +} + +export const initialState: ClearTaskAuditLogState = { + working: false +}; + +const clearTaskAuditLogReducer = createReducer( + initialState, + + on(clearTaskAuditLog, state => ({ ...state, working: true })), + on(taskAuditLogCleared, state => ({ ...state, working: false })), + on(cleartaskAuditLogFailed, state => ({ ...state, working: false })) +); + +export function reducer( + state: ClearTaskAuditLogState | undefined, + action: Action +) { + return clearTaskAuditLogReducer(state, action); +} diff --git a/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.spec.ts b/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.spec.ts index 87b83aef3..fad1b6b71 100644 --- a/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.spec.ts +++ b/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.spec.ts @@ -22,12 +22,9 @@ import { TaskAuditLogState } from './task-audit-log.reducer'; import { - ClearTaskAuditLog, - ClearTaskAuditLogFailed, GetTaskAuditLogEntries, GetTaskAuditLogEntriesFailed, - ReceivedTaskAuditLogEntries, - TaskAuditLogCleared + ReceivedTaskAuditLogEntries } from 'app/backend-status/actions/task-audit-log.actions'; import { TASK_AUDIT_LOG_ENTRY_1, @@ -37,7 +34,7 @@ import { TASK_AUDIT_LOG_ENTRY_5 } from 'app/backend-status/models/task-audit-log-entry.fixtures'; -fdescribe('TaskAuditLog Reducer', () => { +describe('TaskAuditLog Reducer', () => { const LOG_ENTRIES = [ TASK_AUDIT_LOG_ENTRY_1, TASK_AUDIT_LOG_ENTRY_3, @@ -67,10 +64,6 @@ fdescribe('TaskAuditLog Reducer', () => { it('has a last entry date of 0', () => { expect(state.lastEntryDate).toEqual(0); }); - - it('clears the clearing audit log flag', () => { - expect(state.clearingLog).toBeFalsy(); - }); }); describe('fetching a set of entries', () => { @@ -126,43 +119,4 @@ fdescribe('TaskAuditLog Reducer', () => { expect(state.fetchingEntries).toBeFalsy(); }); }); - - describe('when clearing the audit log', () => { - beforeEach(() => { - state = reducer( - { ...state, clearingLog: false }, - new ClearTaskAuditLog() - ); - }); - - it('sets the clearing audit log flag', () => { - expect(state.clearingLog).toBeTruthy(); - }); - }); - - describe('when clearing the audit log succeeds', () => { - beforeEach(() => { - state = reducer( - { ...state, clearingLog: true }, - new TaskAuditLogCleared() - ); - }); - - it('sets the clearing audit log flag', () => { - expect(state.clearingLog).toBeFalsy(); - }); - }); - - describe('when clearing the audit log fails', () => { - beforeEach(() => { - state = reducer( - { ...state, clearingLog: true }, - new ClearTaskAuditLogFailed() - ); - }); - - it('sets the clearing audit log flag', () => { - expect(state.clearingLog).toBeFalsy(); - }); - }); }); diff --git a/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.ts b/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.ts index c07c851e8..e4316f17f 100644 --- a/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.ts +++ b/comixed-frontend/src/app/backend-status/reducers/task-audit-log.reducer.ts @@ -28,14 +28,12 @@ export interface TaskAuditLogState { fetchingEntries: boolean; entries: TaskAuditLogEntry[]; lastEntryDate: number; - clearingLog: boolean; } export const initialState: TaskAuditLogState = { fetchingEntries: false, entries: [], - lastEntryDate: 0, - clearingLog: false + lastEntryDate: 0 }; export function reducer( @@ -65,15 +63,6 @@ export function reducer( case TaskAuditLogActionTypes.GetEntriesFailed: return { ...state, fetchingEntries: false }; - case TaskAuditLogActionTypes.ClearLog: - return { ...state, clearingLog: true }; - - case TaskAuditLogActionTypes.LogCleared: - return { ...state, clearingLog: false }; - - case TaskAuditLogActionTypes.ClearLogFailed: - return { ...state, clearingLog: false }; - default: return state; } diff --git a/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.spec.ts b/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.spec.ts new file mode 100644 index 000000000..669cec65a --- /dev/null +++ b/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.spec.ts @@ -0,0 +1,50 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { + CLEAR_TASK_AUDIT_LOG_FEATURE_KEY, + ClearTaskAuditLogState +} from '../reducers/clear-task-audit-log.reducer'; +import { + selectClearTaskAuditLogState, + selectClearTaskingAuditLogWorking +} from './clear-task-audit-log.selectors'; + +describe('ClearTaskAuditLog Selectors', () => { + let state: ClearTaskAuditLogState; + + beforeEach(() => { + state = { working: Math.random() * 100 > 50 } as ClearTaskAuditLogState; + }); + + it('returns the feature state', () => { + expect( + selectClearTaskAuditLogState({ + [CLEAR_TASK_AUDIT_LOG_FEATURE_KEY]: state + }) + ).toEqual(state); + }); + + it('returns the working state', () => { + expect( + selectClearTaskingAuditLogWorking({ + [CLEAR_TASK_AUDIT_LOG_FEATURE_KEY]: state + }) + ).toEqual(state.working); + }); +}); diff --git a/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.ts b/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.ts new file mode 100644 index 000000000..7bee28837 --- /dev/null +++ b/comixed-frontend/src/app/backend-status/selectors/clear-task-audit-log.selectors.ts @@ -0,0 +1,36 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { createFeatureSelector, createSelector } from '@ngrx/store'; +import * as fromClearTaskAuditLog from '../reducers/clear-task-audit-log.reducer'; +import { ClearTaskAuditLogState } from '../reducers/clear-task-audit-log.reducer'; + +/** + * Returns the clear task audit log state. + */ +export const selectClearTaskAuditLogState = createFeatureSelector< + fromClearTaskAuditLog.ClearTaskAuditLogState +>(fromClearTaskAuditLog.CLEAR_TASK_AUDIT_LOG_FEATURE_KEY); + +/** + * Returns the working state for clearing the task audit log. + */ +export const selectClearTaskingAuditLogWorking = createSelector( + selectClearTaskAuditLogState, + (state: ClearTaskAuditLogState) => state.working +); diff --git a/comixed-frontend/src/app/backend-status/services/task-audit-log.service.spec.ts b/comixed-frontend/src/app/backend-status/services/task-audit-log.service.spec.ts index 0d3fe0d2b..1ab34744a 100644 --- a/comixed-frontend/src/app/backend-status/services/task-audit-log.service.spec.ts +++ b/comixed-frontend/src/app/backend-status/services/task-audit-log.service.spec.ts @@ -32,7 +32,11 @@ import { HttpTestingController } from '@angular/common/http/testing'; import { interpolate } from 'app/app.functions'; -import { GET_TASK_LOG_ENTRIES_URL } from 'app/backend-status/backend-status.constants'; +import { + CLEAR_TASK_AUDIT_LOG_URL, + GET_TASK_LOG_ENTRIES_URL +} from 'app/backend-status/backend-status.constants'; +import { ApiResponse } from 'app/core'; describe('TaskAuditLogService', () => { const LAST_ENTRY_DATE = new Date().getTime(); @@ -74,4 +78,17 @@ describe('TaskAuditLogService', () => { expect(req.request.method).toEqual('GET'); req.flush(LOG_ENTRIES); }); + + it('can clear the task audit log', () => { + const success = Math.random() * 100 > 50; + taskAuditLogService + .clearAuditLog() + .subscribe(response => + expect(response).toEqual({ success } as ApiResponse) + ); + + const req = httpMock.expectOne(interpolate(CLEAR_TASK_AUDIT_LOG_URL)); + expect(req.request.method).toEqual('DELETE'); + req.flush({ success } as ApiResponse); + }); }); diff --git a/comixed-frontend/src/app/backend-status/services/task-audit-log.service.ts b/comixed-frontend/src/app/backend-status/services/task-audit-log.service.ts index 60dd3954f..e03ee3728 100644 --- a/comixed-frontend/src/app/backend-status/services/task-audit-log.service.ts +++ b/comixed-frontend/src/app/backend-status/services/task-audit-log.service.ts @@ -20,7 +20,10 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { LoggerService } from '@angular-ru/logger'; import { HttpClient } from '@angular/common/http'; -import { GET_TASK_LOG_ENTRIES_URL } from 'app/backend-status/backend-status.constants'; +import { + CLEAR_TASK_AUDIT_LOG_URL, + GET_TASK_LOG_ENTRIES_URL +} from 'app/backend-status/backend-status.constants'; import { interpolate } from 'app/app.functions'; @Injectable({ @@ -30,12 +33,17 @@ export class TaskAuditLogService { constructor(private logger: LoggerService, private http: HttpClient) {} getLogEntries(timestamp: number): Observable { - this.logger.debug( - '[GET] http request: get task audit log entries:', - timestamp - ); + this.logger.debug('service: get task audit log entries:', timestamp); return this.http.get( interpolate(GET_TASK_LOG_ENTRIES_URL, { timestamp: timestamp }) ); } + + /** + * Sends a request to clear the task audit log. + */ + clearAuditLog(): Observable { + this.logger.debug('service: send request to clear the task audit log'); + return this.http.delete(interpolate(CLEAR_TASK_AUDIT_LOG_URL)); + } } diff --git a/comixed-frontend/src/app/core/core.module.ts b/comixed-frontend/src/app/core/core.module.ts index 175803448..75de5a22e 100644 --- a/comixed-frontend/src/app/core/core.module.ts +++ b/comixed-frontend/src/app/core/core.module.ts @@ -18,6 +18,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { AlertService } from 'app/core/services/alert.service'; /** * The core module contains code that is shared between all other modules. @@ -26,6 +27,7 @@ import { CommonModule } from '@angular/common'; */ @NgModule({ declarations: [], - imports: [CommonModule] + imports: [CommonModule], + providers: [AlertService] }) export class CoreModule {} diff --git a/comixed-frontend/src/app/core/index.ts b/comixed-frontend/src/app/core/index.ts index c60d7ce46..770526721 100644 --- a/comixed-frontend/src/app/core/index.ts +++ b/comixed-frontend/src/app/core/index.ts @@ -17,3 +17,4 @@ */ export * from './models/api-response'; +export * from './services/alert.service'; diff --git a/comixed-frontend/src/app/core/services/alert.service.spec.ts b/comixed-frontend/src/app/core/services/alert.service.spec.ts new file mode 100644 index 000000000..26b6dbb94 --- /dev/null +++ b/comixed-frontend/src/app/core/services/alert.service.spec.ts @@ -0,0 +1,30 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { TestBed } from '@angular/core/testing'; + +import { AlertService } from './alert.service'; + +describe('AlertService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: AlertService = TestBed.get(AlertService); + expect(service).toBeTruthy(); + }); +}); diff --git a/comixed-frontend/src/app/core/services/alert.service.ts b/comixed-frontend/src/app/core/services/alert.service.ts new file mode 100644 index 000000000..ad540ec70 --- /dev/null +++ b/comixed-frontend/src/app/core/services/alert.service.ts @@ -0,0 +1,33 @@ +/* + * ComiXed - A digital comic book library management application. + * Copyright (C) 2020, The ComiXed Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +import { Injectable } from '@angular/core'; + +/** + * Provides methods for sending alerts to the browser. + */ +@Injectable({ + providedIn: 'root' +}) +export class AlertService { + constructor() {} + + info(text: string): void {} + + error(text: string): void {} +} diff --git a/comixed-frontend/src/assets/i18n/en/backend-status.json b/comixed-frontend/src/assets/i18n/en/backend-status.json index d2453c881..a128fad0a 100644 --- a/comixed-frontend/src/assets/i18n/en/backend-status.json +++ b/comixed-frontend/src/assets/i18n/en/backend-status.json @@ -39,5 +39,22 @@ "description": "Description" } } + }, + "task": { + "clear-audit-log": { + "tooltip": "Clear the task audit log...", + "confirm-header": "Clear Task Audit Log", + "confirm-message": "Are you sure you want to clear the task audit log?", + "effects": { + "clear-audit-log": { + "success": { + "detail": "The task audit log is cleared..." + }, + "error": { + "detail": "Failed to clear the task audit log..." + } + } + } + } } } diff --git a/comixed-frontend/src/assets/i18n/es/backend-status.json b/comixed-frontend/src/assets/i18n/es/backend-status.json index 91c38501e..09b058c9c 100644 --- a/comixed-frontend/src/assets/i18n/es/backend-status.json +++ b/comixed-frontend/src/assets/i18n/es/backend-status.json @@ -39,5 +39,22 @@ "description": "Description" } } + }, + "task": { + "clear-audit-log": { + "tooltip": "Clear the task audit log...", + "confirm-header": "Clear Task Audit Log", + "confirm-message": "Are you sure you want to clear the task audit log?", + "effects": { + "clear-audit-log": { + "success": { + "detail": "The task audit log is cleared..." + }, + "error": { + "detail": "Failed to clear the task audit log..." + } + } + } + } } } diff --git a/comixed-frontend/src/assets/i18n/fr/backend-status.json b/comixed-frontend/src/assets/i18n/fr/backend-status.json index 8df421f57..3cebc2351 100644 --- a/comixed-frontend/src/assets/i18n/fr/backend-status.json +++ b/comixed-frontend/src/assets/i18n/fr/backend-status.json @@ -39,5 +39,22 @@ "description": "Description" } } + }, + "task": { + "clear-audit-log": { + "tooltip": "Clear the task audit log...", + "confirm-header": "Clear Task Audit Log", + "confirm-message": "Are you sure you want to clear the task audit log?", + "effects": { + "clear-audit-log": { + "success": { + "detail": "The task audit log is cleared..." + }, + "error": { + "detail": "Failed to clear the task audit log..." + } + } + } + } } } diff --git a/comixed-frontend/src/assets/i18n/pt/backend-status.json b/comixed-frontend/src/assets/i18n/pt/backend-status.json index d2453c881..a128fad0a 100644 --- a/comixed-frontend/src/assets/i18n/pt/backend-status.json +++ b/comixed-frontend/src/assets/i18n/pt/backend-status.json @@ -39,5 +39,22 @@ "description": "Description" } } + }, + "task": { + "clear-audit-log": { + "tooltip": "Clear the task audit log...", + "confirm-header": "Clear Task Audit Log", + "confirm-message": "Are you sure you want to clear the task audit log?", + "effects": { + "clear-audit-log": { + "success": { + "detail": "The task audit log is cleared..." + }, + "error": { + "detail": "Failed to clear the task audit log..." + } + } + } + } } }