From 53f6ca5efc8bbe9f9c0a038aa8b34a180678dcdd Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 10 Jul 2020 10:10:46 +0100 Subject: [PATCH] Improve the logout experience (#4439) * Add support for including breaking changes in the changelog * Improve the logout experience * Fix unit tests --- .../log-out-dialog.component.spec.ts | 15 ++++--- .../log-out-dialog.component.ts | 10 ++--- .../core/src/features/login/login.module.ts | 4 +- .../core/src/features/login/login.routing.ts | 4 +- .../logout-page/logout-page.component.html | 17 ++++++++ .../logout-page/logout-page.component.scss | 14 +++++++ .../logout-page/logout-page.component.spec.ts | 42 +++++++++++++++++++ .../logout-page/logout-page.component.ts | 35 ++++++++++++++++ .../page-header/page-header.component.ts | 3 +- .../store/src/reducers/auth.reducer.ts | 6 +++ 10 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.html create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts diff --git a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts index b497e9540e..7d15220d14 100644 --- a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts +++ b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts @@ -1,19 +1,21 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Store } from '@ngrx/store'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../test-framework/core-test.modules'; import { SharedModule } from '../../shared/shared.module'; import { CoreModule } from '../core.module'; +import { RouteModule } from './../../app.routing'; import { LogOutDialogComponent } from './log-out-dialog.component'; describe('LogOutDialogComponent', () => { let component: LogOutDialogComponent; let fixture: ComponentFixture; let element: HTMLElement; - let store: any; + let router: any; class MatDialogRefMock { } @@ -30,6 +32,8 @@ describe('LogOutDialogComponent', () => { ], imports: [ CoreModule, + RouterTestingModule, + RouteModule, SharedModule, MatDialogModule, NoopAnimationsModule, @@ -42,7 +46,7 @@ describe('LogOutDialogComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(LogOutDialogComponent); - store = TestBed.get(Store); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); element = fixture.nativeElement; @@ -52,8 +56,8 @@ describe('LogOutDialogComponent', () => { expect(component).toBeTruthy(); }); - it('should dispatch logout action after countdown', fakeAsync(() => { - const spy = spyOn(store, 'dispatch'); + it('should naivgate after countdown', fakeAsync(() => { + const spy = spyOn(router, 'navigate'); component.data = { expiryDate: Date.now() + 1000, @@ -65,6 +69,7 @@ describe('LogOutDialogComponent', () => { expect(spy).not.toHaveBeenCalled(); tick(1500); expect(spy).toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith(['/login/logout']); })); afterEach(() => { diff --git a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts index 477fc342d5..34574f7c51 100644 --- a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts +++ b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts @@ -1,12 +1,9 @@ import { Component, Inject, OnDestroy, OnInit, Optional } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; +import { Router } from '@angular/router'; import { interval, Subscription } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { Logout } from '../../../../store/src/actions/auth.actions'; -import { GeneralEntityAppState } from '../../../../store/src/app-state'; - @Component({ selector: 'app-log-out-dialog', templateUrl: './log-out-dialog.component.html', @@ -16,7 +13,8 @@ export class LogOutDialogComponent implements OnInit, OnDestroy { constructor( public dialogRef: MatDialogRef, @Optional() @Inject(MAT_DIALOG_DATA) public data: any, - private store: Store) { } + private router: Router + ) { } private autoLogout: Subscription; private countDown: number; @@ -33,7 +31,7 @@ export class LogOutDialogComponent implements OnInit, OnDestroy { this.countDown = this.calcCountdown(); if (this.countDown <= 0) { this.autoLogout.unsubscribe(); - this.store.dispatch(new Logout()); + this.router.navigate(['/login/logout']); } else { this.percentage = ((this.countdownTotal - this.countDown) / this.countdownTotal) * 100; } diff --git a/src/frontend/packages/core/src/features/login/login.module.ts b/src/frontend/packages/core/src/features/login/login.module.ts index 5655c0d3a8..97aadbe651 100644 --- a/src/frontend/packages/core/src/features/login/login.module.ts +++ b/src/frontend/packages/core/src/features/login/login.module.ts @@ -4,6 +4,7 @@ import { CoreModule } from '../../core/core.module'; import { SharedModule } from '../../shared/shared.module'; import { LoginPageComponent } from './login-page/login-page.component'; import { LoginRoutingModule } from './login.routing'; +import { LogoutPageComponent } from './logout-page/logout-page.component'; @NgModule({ @@ -13,7 +14,8 @@ import { LoginRoutingModule } from './login.routing'; LoginRoutingModule ], declarations: [ - LoginPageComponent + LoginPageComponent, + LogoutPageComponent ] }) export class LoginModule { } diff --git a/src/frontend/packages/core/src/features/login/login.routing.ts b/src/frontend/packages/core/src/features/login/login.routing.ts index 1ecf12b3d0..5b75a930f7 100644 --- a/src/frontend/packages/core/src/features/login/login.routing.ts +++ b/src/frontend/packages/core/src/features/login/login.routing.ts @@ -2,9 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginPageComponent } from './login-page/login-page.component'; +import { LogoutPageComponent } from './logout-page/logout-page.component'; const loginRoutes: Routes = [ - { path: '', component: LoginPageComponent, } + { path: '', component: LoginPageComponent, }, + { path: 'logout', component: LogoutPageComponent, } ]; @NgModule({ diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.html b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.html new file mode 100644 index 0000000000..e0a4606837 --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.html @@ -0,0 +1,17 @@ + + + +
+
An error occurred logging out
+ +
+ +
+
Logging out
+
+ +
+
+
+
+
diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss new file mode 100644 index 0000000000..c1b0168e6d --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss @@ -0,0 +1,14 @@ +.logout { + &__card { + padding: 0; + width: 300px; + } + &__body { + padding: 24px; + text-align: center; + } + &__msg { + font-size: 18px; + padding-bottom: 20px; + } +} diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts new file mode 100644 index 0000000000..fff7bac83e --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts @@ -0,0 +1,42 @@ +import { CommonModule } from '@angular/common'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CoreModule } from '@angular/flex-layout'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule } from '@ngrx/store'; + +import { appReducers } from '../../../../../store/src/reducers.module'; +import { SharedModule } from '../../../public-api'; +import { LogoutPageComponent } from './logout-page.component'; + +describe('LogoutPageComponent', () => { + let component: LogoutPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LogoutPageComponent ], + imports: [ + CommonModule, + CoreModule, + SharedModule, + RouterTestingModule, + NoopAnimationsModule, + StoreModule.forRoot( + appReducers + ) + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LogoutPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts new file mode 100644 index 0000000000..ed30fc6c6d --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { Logout } from '../../../../../store/src/actions/auth.actions'; +import { AppState } from '../../../../../store/src/app-state'; + +@Component({ + selector: 'app-logout-page', + templateUrl: './logout-page.component.html', + styleUrls: ['./logout-page.component.scss'] +}) +export class LogoutPageComponent implements OnInit { + + public error$: Observable; + + constructor(private store: Store) { + this.error$ = this.store.select(s => s.auth).pipe( + map(auth => auth.error) + ); + } + + ngOnInit() { + // Dispatch the logout action after 1 second - give the logging out screen time to show + setTimeout(() => { + this.store.dispatch(new Logout()); + }, 1000) + } + + reload() { + window.location.assign(window.location.origin); + } + +} diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts index a0ee69b500..46be7b2b61 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts @@ -6,7 +6,6 @@ import * as moment from 'moment'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { Logout } from '../../../../../store/src/actions/auth.actions'; import { ToggleSideNav } from '../../../../../store/src/actions/dashboard-actions'; import { AddRecentlyVisitedEntityAction } from '../../../../../store/src/actions/recently-visited.actions'; import { AppState } from '../../../../../store/src/app-state'; @@ -142,7 +141,7 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit { } logout() { - this.store.dispatch(new Logout()); + this.router.navigate(['/login/logout']); } public toggleSidenav() { diff --git a/src/frontend/packages/store/src/reducers/auth.reducer.ts b/src/frontend/packages/store/src/reducers/auth.reducer.ts index c8ae1c2dd7..e06f551e5a 100644 --- a/src/frontend/packages/store/src/reducers/auth.reducer.ts +++ b/src/frontend/packages/store/src/reducers/auth.reducer.ts @@ -3,6 +3,7 @@ import { LOGIN_FAILED, LOGIN_SUCCESS, LoginFailed, + LOGOUT_FAILED, RESET_AUTH, SESSION_INVALID, SESSION_VERIFIED, @@ -12,6 +13,7 @@ import { RouterActions, RouterNav } from '../actions/router.actions'; import { GET_SYSTEM_INFO_SUCCESS } from '../actions/system.actions'; import { AuthOnlyAppState } from '../app-state'; import { SessionData } from '../types/auth.types'; +import { LogoutFailed } from './../actions/auth.actions'; import { RouterRedirect } from './routing.reducer'; export interface AuthUser { @@ -51,6 +53,10 @@ export function authReducer(state: AuthState = defaultState, action): AuthState case LOGIN_FAILED: const loginFailed = action as LoginFailed; return { ...state, error: true, errorResponse: loginFailed.error, loggingIn: false, loggedIn: false }; + case LOGOUT_FAILED: + const logoutFailed = action as LogoutFailed; + console.error(logoutFailed.error); + return { ...state, loggingIn: false, loggedIn: true, error: true, errorResponse: logoutFailed.error }; case VERIFY_SESSION: return { ...state, error: false, errorResponse: undefined, verifying: true }; case SESSION_VERIFIED: