Skip to content

Commit

Permalink
Added the clear task audit log feature [comixed#424]
Browse files Browse the repository at this point in the history
 * Added a new state to the backend-status module.
 * Added a toolbar to the audit log list page to clear the log.
  • Loading branch information
mcpierce committed Aug 14, 2020
1 parent 43ae163 commit 2c42455
Show file tree
Hide file tree
Showing 27 changed files with 843 additions and 213 deletions.
@@ -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 <http://www.gnu.org/licenses>
*/

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'
);
Expand Up @@ -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 {
Expand All @@ -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;
Expand Up @@ -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`;
18 changes: 16 additions & 2 deletions comixed-frontend/src/app/backend-status/backend-status.module.ts
Expand Up @@ -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';
Expand All @@ -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],
Expand All @@ -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]
Expand Down
@@ -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 <http://www.gnu.org/licenses>
*/

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<any>;
let effects: ClearTaskAuditLogEffects;
let taskAuditLogService: jasmine.SpyObj<TaskAuditLogService>;
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>(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<void>;
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<void>;
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));
});
});
});
@@ -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 <http://www.gnu.org/licenses>
*/

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<void>) =>
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<void>) =>
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());
})
);
});
}

0 comments on commit 2c42455

Please sign in to comment.