From 5950f27230b46d5f8ff67ae6d96a31592be2c6fc Mon Sep 17 00:00:00 2001 From: roberto Date: Thu, 11 Mar 2021 19:00:09 -0500 Subject: [PATCH 1/9] feat: consume user info --- debug.log | 8 +++++ .../login/services/azure.ad.b2c.service.ts | 4 +++ .../components/user/store/user.actions.ts | 33 +++++++++++++++++++ .../components/user/store/user.effects.ts | 30 +++++++++++++++++ .../components/user/store/user.reducer.ts | 24 ++++++++++++++ .../components/user/store/user.selectors.ts | 6 ++++ .../components/user/store/user.service.ts | 21 ++++++++++++ .../shared/components/user/user.component.ts | 18 +++++++++- src/app/reducers/index.ts | 7 ++-- 9 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 debug.log create mode 100644 src/app/modules/shared/components/user/store/user.actions.ts create mode 100644 src/app/modules/shared/components/user/store/user.effects.ts create mode 100644 src/app/modules/shared/components/user/store/user.reducer.ts create mode 100644 src/app/modules/shared/components/user/store/user.selectors.ts create mode 100644 src/app/modules/shared/components/user/store/user.service.ts diff --git a/debug.log b/debug.log new file mode 100644 index 000000000..04d6ae9ee --- /dev/null +++ b/debug.log @@ -0,0 +1,8 @@ +[1207/120956.471:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1209/102017.100:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1209/114251.202:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1211/010054.703:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1211/165324.600:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1214/163635.420:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1218/134332.967:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) +[1218/155414.096:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) diff --git a/src/app/modules/login/services/azure.ad.b2c.service.ts b/src/app/modules/login/services/azure.ad.b2c.service.ts index 7f8ed66b4..f4e486f60 100644 --- a/src/app/modules/login/services/azure.ad.b2c.service.ts +++ b/src/app/modules/login/services/azure.ad.b2c.service.ts @@ -83,4 +83,8 @@ export class AzureAdB2CService { getUserGroup(): string { return this.msal.getAccount().idToken?.extension_role; } + + getUserId(): string{ + return this.msal.getAccount().accountIdentifier; + } } diff --git a/src/app/modules/shared/components/user/store/user.actions.ts b/src/app/modules/shared/components/user/store/user.actions.ts new file mode 100644 index 000000000..42b768628 --- /dev/null +++ b/src/app/modules/shared/components/user/store/user.actions.ts @@ -0,0 +1,33 @@ +import { Action } from '@ngrx/store'; + +export enum UserActionTypes { + LOAD_USER = '[User] LOAD_USER', + LOAD_USER_SUCCESS = '[User] LOAD_USER_SUCCESS', + LOAD_USER_FAIL = '[User] LOAD_USER_FAIL', +} + +export class LoadUser implements Action { + public readonly type = UserActionTypes.LOAD_USER; + constructor(readonly userId) { + } +} + +export class LoadUserSuccess implements Action { + readonly type = UserActionTypes.LOAD_USER_SUCCESS; + + constructor(readonly payload) { + } +} + +export class LoadUserFail implements Action { + public readonly type = UserActionTypes.LOAD_USER_FAIL; + + constructor(public error: string) { + } +} + + +export type UserActions = + | LoadUser + | LoadUserSuccess + | LoadUserFail diff --git a/src/app/modules/shared/components/user/store/user.effects.ts b/src/app/modules/shared/components/user/store/user.effects.ts new file mode 100644 index 000000000..128b43f5b --- /dev/null +++ b/src/app/modules/shared/components/user/store/user.effects.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Action } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; +import { UserService } from './user.service'; +import * as actions from './user.actions'; + +@Injectable() +export class UserEffects { + constructor(private actions$: Actions, private userService: UserService) { + } + + @Effect() + loadUser$: Observable = this.actions$.pipe( + ofType(actions.UserActionTypes.LOAD_USER), + map((action: actions.LoadUser) => action.userId), + mergeMap((userId) => + this.userService.loadUser(userId).pipe( + map((response) => { + return new actions.LoadUserSuccess(response); + }), + catchError((error) => { + return of(new actions.LoadUserFail(error)); + }) + ) + ) + ); + +} diff --git a/src/app/modules/shared/components/user/store/user.reducer.ts b/src/app/modules/shared/components/user/store/user.reducer.ts new file mode 100644 index 000000000..9f53df9fa --- /dev/null +++ b/src/app/modules/shared/components/user/store/user.reducer.ts @@ -0,0 +1,24 @@ +import { UserActions, UserActionTypes } from './user.actions'; + +export const initialState = { + name:'', + groups:[] +}; + +export const userReducer = (state: any = initialState, action: UserActions): any => { + switch (action.type) { + case UserActionTypes.LOAD_USER: + return state; + case UserActionTypes.LOAD_USER_SUCCESS: + return { + ...state, + name: action.payload.name, + groups: action.payload.groups + }; + case UserActionTypes.LOAD_USER_FAIL: + return state; + default: { + return state; + } + } +}; diff --git a/src/app/modules/shared/components/user/store/user.selectors.ts b/src/app/modules/shared/components/user/store/user.selectors.ts new file mode 100644 index 000000000..3d56bf836 --- /dev/null +++ b/src/app/modules/shared/components/user/store/user.selectors.ts @@ -0,0 +1,6 @@ +import { createFeatureSelector, createSelector } from '@ngrx/store'; + +const getUserState = createFeatureSelector('user'); + +export const getUserInfo = createSelector(getUserState, (state: any) => state); + diff --git a/src/app/modules/shared/components/user/store/user.service.ts b/src/app/modules/shared/components/user/store/user.service.ts new file mode 100644 index 000000000..d77c64906 --- /dev/null +++ b/src/app/modules/shared/components/user/store/user.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class UserService { + + constructor(private http: HttpClient) { + } + + baseUrl = `${environment.timeTrackerApiUrl}/users`; + + loadUser(userId): Observable { + return this.http.get(`${this.baseUrl}/${userId}`); + } + +} diff --git a/src/app/modules/shared/components/user/user.component.ts b/src/app/modules/shared/components/user/user.component.ts index 5f99f7399..afaa17891 100644 --- a/src/app/modules/shared/components/user/user.component.ts +++ b/src/app/modules/shared/components/user/user.component.ts @@ -1,6 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service'; +import { select, Store } from '@ngrx/store'; +import { getUserInfo } from './store/user.selectors'; +import { Subscription } from 'rxjs'; +import { LoadUser } from './store/user.actions'; + @Component({ selector: 'app-user', templateUrl: './user.component.html', @@ -8,12 +13,23 @@ import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service' }) export class UserComponent implements OnInit { name: string; + userSubscription: Subscription; - constructor(private azureAdB2CService: AzureAdB2CService) {} + constructor( + private azureAdB2CService: AzureAdB2CService, + private store: Store) {} ngOnInit(): void { if (this.azureAdB2CService.isLogin()) { this.name = this.azureAdB2CService.getName(); + let userId = this.azureAdB2CService.getUserId(); + + this.store.dispatch(new LoadUser(userId)); + this.userSubscription = this.store + .pipe(select(getUserInfo)) + .subscribe((response) => { + console.log(response) + }) this.azureAdB2CService.setTenantId(); } } diff --git a/src/app/reducers/index.ts b/src/app/reducers/index.ts index 6e1316651..1e46ded5b 100644 --- a/src/app/reducers/index.ts +++ b/src/app/reducers/index.ts @@ -6,7 +6,8 @@ import { customerManagementReducer } from '../modules/customer-management/store/ import { projectTypeReducer } from '../modules/customer-management/components/projects-type/store/project-type.reducers'; import { entryReducer } from '../modules/time-clock/store/entry.reducer'; import { environment } from '../../environments/environment'; -import { userReducer } from '../modules/users/store/user.reducers'; +import { userReducer } from '../modules/shared/components/user/store/user.reducer'; +import { userReducer as usersReducer } from '../modules/users/store/user.reducers'; export interface State { projects; activities; @@ -15,6 +16,7 @@ export interface State { projectType; entries; users; + user } export const reducers: ActionReducerMap = { @@ -24,7 +26,8 @@ export const reducers: ActionReducerMap = { technologies: technologyReducer, projectType: projectTypeReducer, entries: entryReducer, - users: userReducer, + users: usersReducer, + user: userReducer, }; export const metaReducers: MetaReducer[] = !environment.production ? [] : []; From 88ae09d343e5a38c3aa291317a3835de34763371 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Fri, 12 Mar 2021 17:28:53 -0500 Subject: [PATCH 2/9] feat: TT-155 register userEfecct in AppModule --- src/app/app.module.ts | 14 +++++++------ .../modules/login/store/user.actions.spec.ts | 0 .../user => login}/store/user.actions.ts | 17 +++++----------- .../modules/login/store/user.effects.spec.ts | 0 .../user => login}/store/user.effects.ts | 18 ++++++----------- .../modules/login/store/user.reducer.spec.ts | 0 .../user => login}/store/user.reducer.ts | 6 +++--- .../login/store/user.selectors.spec.ts | 0 .../user => login}/store/user.selectors.ts | 3 +-- .../user => login}/store/user.service.ts | 0 .../shared/components/user/user.component.ts | 20 ++++++++----------- src/app/reducers/index.ts | 4 ++-- 12 files changed, 33 insertions(+), 49 deletions(-) create mode 100644 src/app/modules/login/store/user.actions.spec.ts rename src/app/modules/{shared/components/user => login}/store/user.actions.ts (64%) create mode 100644 src/app/modules/login/store/user.effects.spec.ts rename src/app/modules/{shared/components/user => login}/store/user.effects.ts (57%) create mode 100644 src/app/modules/login/store/user.reducer.spec.ts rename src/app/modules/{shared/components/user => login}/store/user.reducer.ts (88%) create mode 100644 src/app/modules/login/store/user.selectors.spec.ts rename src/app/modules/{shared/components/user => login}/store/user.selectors.ts (72%) rename src/app/modules/{shared/components/user => login}/store/user.service.ts (100%) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 755dcd5da..9f07e7156 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -57,7 +57,8 @@ import { ProjectTypeListComponent } from './modules/customer-management/componen // tslint:disable-next-line: max-line-length import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component'; import { CustomerEffects } from './modules/customer-management/store/customer-management.effects'; -import { UserEffects } from './modules/users/store/user.effects'; +import { UserEffects as UsersEffects } from './modules/users/store/user.effects'; +import { UserEffects } from './modules/login/store/user.effects'; import { EntryEffects } from './modules/time-clock/store/entry.effects'; import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor'; import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe'; @@ -74,7 +75,7 @@ import { LoadingBarComponent } from './modules/shared/components/loading-bar/loa import { UsersComponent } from './modules/users/pages/users.component'; import { UsersListComponent } from './modules/users/components/users-list/users-list.component'; import { UiSwitchModule } from 'ngx-ui-switch'; -import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; +import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; // tslint:disable-next-line: max-line-length import { TechnologyReportTableComponent } from './modules/technology-report/components/technology-report-table/technology-report-table.component'; import { TechnologyReportComponent } from './modules/technology-report/pages/technology-report.component'; @@ -151,8 +152,8 @@ const maskConfig: Partial = { }), !environment.production ? StoreDevtoolsModule.instrument({ - maxAge: 15, // Retains last 15 states - }) + maxAge: 15, // Retains last 15 states + }) : [], EffectsModule.forRoot([ ProjectEffects, @@ -161,9 +162,10 @@ const maskConfig: Partial = { TechnologyEffects, ProjectTypeEffects, EntryEffects, + UsersEffects, UserEffects, ]), - ToastrModule.forRoot() + ToastrModule.forRoot(), ], providers: [ { @@ -176,4 +178,4 @@ const maskConfig: Partial = { ], bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/src/app/modules/login/store/user.actions.spec.ts b/src/app/modules/login/store/user.actions.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/shared/components/user/store/user.actions.ts b/src/app/modules/login/store/user.actions.ts similarity index 64% rename from src/app/modules/shared/components/user/store/user.actions.ts rename to src/app/modules/login/store/user.actions.ts index 42b768628..2b783252e 100644 --- a/src/app/modules/shared/components/user/store/user.actions.ts +++ b/src/app/modules/login/store/user.actions.ts @@ -8,26 +8,19 @@ export enum UserActionTypes { export class LoadUser implements Action { public readonly type = UserActionTypes.LOAD_USER; - constructor(readonly userId) { - } + constructor(readonly userId: string) {} } export class LoadUserSuccess implements Action { - readonly type = UserActionTypes.LOAD_USER_SUCCESS; + public readonly type = UserActionTypes.LOAD_USER_SUCCESS; - constructor(readonly payload) { - } + constructor(readonly payload: any) {} } export class LoadUserFail implements Action { public readonly type = UserActionTypes.LOAD_USER_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } - -export type UserActions = - | LoadUser - | LoadUserSuccess - | LoadUserFail +export type UserActions = LoadUser | LoadUserSuccess | LoadUserFail; diff --git a/src/app/modules/login/store/user.effects.spec.ts b/src/app/modules/login/store/user.effects.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/shared/components/user/store/user.effects.ts b/src/app/modules/login/store/user.effects.ts similarity index 57% rename from src/app/modules/shared/components/user/store/user.effects.ts rename to src/app/modules/login/store/user.effects.ts index 128b43f5b..4322699a2 100644 --- a/src/app/modules/shared/components/user/store/user.effects.ts +++ b/src/app/modules/login/store/user.effects.ts @@ -1,30 +1,24 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, ofType, Effect } from '@ngrx/effects'; import { Action } from '@ngrx/store'; import { Observable, of } from 'rxjs'; -import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; +import { catchError, map, mergeMap } from 'rxjs/operators'; import { UserService } from './user.service'; import * as actions from './user.actions'; @Injectable() export class UserEffects { - constructor(private actions$: Actions, private userService: UserService) { - } + constructor(private actions$: Actions, private userService: UserService) {} @Effect() - loadUser$: Observable = this.actions$.pipe( + loadUserInfo$: Observable = this.actions$.pipe( ofType(actions.UserActionTypes.LOAD_USER), map((action: actions.LoadUser) => action.userId), mergeMap((userId) => this.userService.loadUser(userId).pipe( - map((response) => { - return new actions.LoadUserSuccess(response); - }), - catchError((error) => { - return of(new actions.LoadUserFail(error)); - }) + map((response) => new actions.LoadUserSuccess(response)), + catchError((error) => of(new actions.LoadUserFail(error))) ) ) ); - } diff --git a/src/app/modules/login/store/user.reducer.spec.ts b/src/app/modules/login/store/user.reducer.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/shared/components/user/store/user.reducer.ts b/src/app/modules/login/store/user.reducer.ts similarity index 88% rename from src/app/modules/shared/components/user/store/user.reducer.ts rename to src/app/modules/login/store/user.reducer.ts index 9f53df9fa..b9ef0b8a1 100644 --- a/src/app/modules/shared/components/user/store/user.reducer.ts +++ b/src/app/modules/login/store/user.reducer.ts @@ -1,8 +1,8 @@ import { UserActions, UserActionTypes } from './user.actions'; export const initialState = { - name:'', - groups:[] + name: '', + groups: [], }; export const userReducer = (state: any = initialState, action: UserActions): any => { @@ -13,7 +13,7 @@ export const userReducer = (state: any = initialState, action: UserActions): any return { ...state, name: action.payload.name, - groups: action.payload.groups + groups: action.payload.groups, }; case UserActionTypes.LOAD_USER_FAIL: return state; diff --git a/src/app/modules/login/store/user.selectors.spec.ts b/src/app/modules/login/store/user.selectors.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/shared/components/user/store/user.selectors.ts b/src/app/modules/login/store/user.selectors.ts similarity index 72% rename from src/app/modules/shared/components/user/store/user.selectors.ts rename to src/app/modules/login/store/user.selectors.ts index 3d56bf836..1d0cc857f 100644 --- a/src/app/modules/shared/components/user/store/user.selectors.ts +++ b/src/app/modules/login/store/user.selectors.ts @@ -1,6 +1,5 @@ import { createFeatureSelector, createSelector } from '@ngrx/store'; -const getUserState = createFeatureSelector('user'); +const getUserState = createFeatureSelector('user'); export const getUserInfo = createSelector(getUserState, (state: any) => state); - diff --git a/src/app/modules/shared/components/user/store/user.service.ts b/src/app/modules/login/store/user.service.ts similarity index 100% rename from src/app/modules/shared/components/user/store/user.service.ts rename to src/app/modules/login/store/user.service.ts diff --git a/src/app/modules/shared/components/user/user.component.ts b/src/app/modules/shared/components/user/user.component.ts index afaa17891..63cff443a 100644 --- a/src/app/modules/shared/components/user/user.component.ts +++ b/src/app/modules/shared/components/user/user.component.ts @@ -2,9 +2,9 @@ import { Component, OnInit } from '@angular/core'; import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service'; import { select, Store } from '@ngrx/store'; -import { getUserInfo } from './store/user.selectors'; +import { getUserInfo } from '../../../login/store/user.selectors'; import { Subscription } from 'rxjs'; -import { LoadUser } from './store/user.actions'; +import { LoadUser } from '../../../login/store/user.actions'; @Component({ selector: 'app-user', @@ -15,21 +15,17 @@ export class UserComponent implements OnInit { name: string; userSubscription: Subscription; - constructor( - private azureAdB2CService: AzureAdB2CService, - private store: Store) {} + constructor(private azureAdB2CService: AzureAdB2CService, private store: Store) {} ngOnInit(): void { if (this.azureAdB2CService.isLogin()) { this.name = this.azureAdB2CService.getName(); - let userId = this.azureAdB2CService.getUserId(); - + const userId = this.azureAdB2CService.getUserId(); + this.store.dispatch(new LoadUser(userId)); - this.userSubscription = this.store - .pipe(select(getUserInfo)) - .subscribe((response) => { - console.log(response) - }) + this.userSubscription = this.store.pipe(select(getUserInfo)).subscribe((response) => { + console.log('from Store: ', response); + }); this.azureAdB2CService.setTenantId(); } } diff --git a/src/app/reducers/index.ts b/src/app/reducers/index.ts index 1e46ded5b..77ff066ac 100644 --- a/src/app/reducers/index.ts +++ b/src/app/reducers/index.ts @@ -6,7 +6,7 @@ import { customerManagementReducer } from '../modules/customer-management/store/ import { projectTypeReducer } from '../modules/customer-management/components/projects-type/store/project-type.reducers'; import { entryReducer } from '../modules/time-clock/store/entry.reducer'; import { environment } from '../../environments/environment'; -import { userReducer } from '../modules/shared/components/user/store/user.reducer'; +import { userReducer } from '../modules/login/store/user.reducer'; import { userReducer as usersReducer } from '../modules/users/store/user.reducers'; export interface State { projects; @@ -16,7 +16,7 @@ export interface State { projectType; entries; users; - user + user; } export const reducers: ActionReducerMap = { From cf2c16a9a8748065b5f53d8a76b028e9ffae7abc Mon Sep 17 00:00:00 2001 From: roberto Date: Fri, 12 Mar 2021 18:29:03 -0500 Subject: [PATCH 3/9] feat: TT-155 create user module and model --- src/app/app.module.ts | 2 +- .../shared/components/user/user.component.ts | 13 +------------ src/app/modules/user/models/user.ts | 9 +++++++++ .../{login => user}/store/user.actions.spec.ts | 0 .../modules/{login => user}/store/user.actions.ts | 3 ++- .../{login => user}/store/user.effects.spec.ts | 0 .../modules/{login => user}/store/user.effects.ts | 0 .../{login => user}/store/user.reducer.spec.ts | 0 .../modules/{login => user}/store/user.reducer.ts | 8 +++----- .../{login => user}/store/user.selectors.spec.ts | 0 .../modules/{login => user}/store/user.selectors.ts | 0 .../modules/{login => user}/store/user.service.ts | 0 src/app/reducers/index.ts | 2 +- 13 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 src/app/modules/user/models/user.ts rename src/app/modules/{login => user}/store/user.actions.spec.ts (100%) rename src/app/modules/{login => user}/store/user.actions.ts (89%) rename src/app/modules/{login => user}/store/user.effects.spec.ts (100%) rename src/app/modules/{login => user}/store/user.effects.ts (100%) rename src/app/modules/{login => user}/store/user.reducer.spec.ts (100%) rename src/app/modules/{login => user}/store/user.reducer.ts (79%) rename src/app/modules/{login => user}/store/user.selectors.spec.ts (100%) rename src/app/modules/{login => user}/store/user.selectors.ts (100%) rename src/app/modules/{login => user}/store/user.service.ts (100%) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9f07e7156..f818512c2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -58,7 +58,7 @@ import { ProjectTypeListComponent } from './modules/customer-management/componen import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component'; import { CustomerEffects } from './modules/customer-management/store/customer-management.effects'; import { UserEffects as UsersEffects } from './modules/users/store/user.effects'; -import { UserEffects } from './modules/login/store/user.effects'; +import { UserEffects } from './modules/user/store/user.effects'; import { EntryEffects } from './modules/time-clock/store/entry.effects'; import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor'; import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe'; diff --git a/src/app/modules/shared/components/user/user.component.ts b/src/app/modules/shared/components/user/user.component.ts index 63cff443a..798c8e0c2 100644 --- a/src/app/modules/shared/components/user/user.component.ts +++ b/src/app/modules/shared/components/user/user.component.ts @@ -1,10 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service'; -import { select, Store } from '@ngrx/store'; -import { getUserInfo } from '../../../login/store/user.selectors'; -import { Subscription } from 'rxjs'; -import { LoadUser } from '../../../login/store/user.actions'; @Component({ selector: 'app-user', @@ -13,19 +9,12 @@ import { LoadUser } from '../../../login/store/user.actions'; }) export class UserComponent implements OnInit { name: string; - userSubscription: Subscription; - constructor(private azureAdB2CService: AzureAdB2CService, private store: Store) {} + constructor(private azureAdB2CService: AzureAdB2CService) {} ngOnInit(): void { if (this.azureAdB2CService.isLogin()) { this.name = this.azureAdB2CService.getName(); - const userId = this.azureAdB2CService.getUserId(); - - this.store.dispatch(new LoadUser(userId)); - this.userSubscription = this.store.pipe(select(getUserInfo)).subscribe((response) => { - console.log('from Store: ', response); - }); this.azureAdB2CService.setTenantId(); } } diff --git a/src/app/modules/user/models/user.ts b/src/app/modules/user/models/user.ts new file mode 100644 index 000000000..750a95d86 --- /dev/null +++ b/src/app/modules/user/models/user.ts @@ -0,0 +1,9 @@ +export interface User { + name: string; + email: string; + roles?: string[]; + groups?: string[]; + id: string; + tenant_id?: string; + deleted?: string; +} diff --git a/src/app/modules/login/store/user.actions.spec.ts b/src/app/modules/user/store/user.actions.spec.ts similarity index 100% rename from src/app/modules/login/store/user.actions.spec.ts rename to src/app/modules/user/store/user.actions.spec.ts diff --git a/src/app/modules/login/store/user.actions.ts b/src/app/modules/user/store/user.actions.ts similarity index 89% rename from src/app/modules/login/store/user.actions.ts rename to src/app/modules/user/store/user.actions.ts index 2b783252e..b5b5a664b 100644 --- a/src/app/modules/login/store/user.actions.ts +++ b/src/app/modules/user/store/user.actions.ts @@ -1,4 +1,5 @@ import { Action } from '@ngrx/store'; +import { User } from '../models/user'; export enum UserActionTypes { LOAD_USER = '[User] LOAD_USER', @@ -14,7 +15,7 @@ export class LoadUser implements Action { export class LoadUserSuccess implements Action { public readonly type = UserActionTypes.LOAD_USER_SUCCESS; - constructor(readonly payload: any) {} + constructor(readonly payload: User) {} } export class LoadUserFail implements Action { diff --git a/src/app/modules/login/store/user.effects.spec.ts b/src/app/modules/user/store/user.effects.spec.ts similarity index 100% rename from src/app/modules/login/store/user.effects.spec.ts rename to src/app/modules/user/store/user.effects.spec.ts diff --git a/src/app/modules/login/store/user.effects.ts b/src/app/modules/user/store/user.effects.ts similarity index 100% rename from src/app/modules/login/store/user.effects.ts rename to src/app/modules/user/store/user.effects.ts diff --git a/src/app/modules/login/store/user.reducer.spec.ts b/src/app/modules/user/store/user.reducer.spec.ts similarity index 100% rename from src/app/modules/login/store/user.reducer.spec.ts rename to src/app/modules/user/store/user.reducer.spec.ts diff --git a/src/app/modules/login/store/user.reducer.ts b/src/app/modules/user/store/user.reducer.ts similarity index 79% rename from src/app/modules/login/store/user.reducer.ts rename to src/app/modules/user/store/user.reducer.ts index b9ef0b8a1..dd10eae49 100644 --- a/src/app/modules/login/store/user.reducer.ts +++ b/src/app/modules/user/store/user.reducer.ts @@ -2,6 +2,8 @@ import { UserActions, UserActionTypes } from './user.actions'; export const initialState = { name: '', + email: '', + roles: [], groups: [], }; @@ -10,11 +12,7 @@ export const userReducer = (state: any = initialState, action: UserActions): any case UserActionTypes.LOAD_USER: return state; case UserActionTypes.LOAD_USER_SUCCESS: - return { - ...state, - name: action.payload.name, - groups: action.payload.groups, - }; + return action.payload; case UserActionTypes.LOAD_USER_FAIL: return state; default: { diff --git a/src/app/modules/login/store/user.selectors.spec.ts b/src/app/modules/user/store/user.selectors.spec.ts similarity index 100% rename from src/app/modules/login/store/user.selectors.spec.ts rename to src/app/modules/user/store/user.selectors.spec.ts diff --git a/src/app/modules/login/store/user.selectors.ts b/src/app/modules/user/store/user.selectors.ts similarity index 100% rename from src/app/modules/login/store/user.selectors.ts rename to src/app/modules/user/store/user.selectors.ts diff --git a/src/app/modules/login/store/user.service.ts b/src/app/modules/user/store/user.service.ts similarity index 100% rename from src/app/modules/login/store/user.service.ts rename to src/app/modules/user/store/user.service.ts diff --git a/src/app/reducers/index.ts b/src/app/reducers/index.ts index 77ff066ac..5e0f8585d 100644 --- a/src/app/reducers/index.ts +++ b/src/app/reducers/index.ts @@ -6,7 +6,7 @@ import { customerManagementReducer } from '../modules/customer-management/store/ import { projectTypeReducer } from '../modules/customer-management/components/projects-type/store/project-type.reducers'; import { entryReducer } from '../modules/time-clock/store/entry.reducer'; import { environment } from '../../environments/environment'; -import { userReducer } from '../modules/login/store/user.reducer'; +import { userReducer } from '../modules/user/store/user.reducer'; import { userReducer as usersReducer } from '../modules/users/store/user.reducers'; export interface State { projects; From a9702346743d3d7de06a572a40e0bc6630636e42 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Mon, 15 Mar 2021 16:12:10 -0500 Subject: [PATCH 4/9] test: TT-155 Added unit tests for user ngrx flow --- .../modules/user/store/user.actions.spec.ts | 28 +++++++++ .../modules/user/store/user.effects.spec.ts | 62 +++++++++++++++++++ src/app/modules/user/store/user.effects.ts | 2 +- .../modules/user/store/user.reducer.spec.ts | 44 +++++++++++++ .../modules/user/store/user.selectors.spec.ts | 21 +++++++ src/app/modules/user/store/user.service.ts | 21 ------- 6 files changed, 156 insertions(+), 22 deletions(-) delete mode 100644 src/app/modules/user/store/user.service.ts diff --git a/src/app/modules/user/store/user.actions.spec.ts b/src/app/modules/user/store/user.actions.spec.ts index e69de29bb..bcae1136a 100644 --- a/src/app/modules/user/store/user.actions.spec.ts +++ b/src/app/modules/user/store/user.actions.spec.ts @@ -0,0 +1,28 @@ +import { LoadUserFail, LoadUserSuccess, UserActionTypes } from './user.actions'; + +import { User } from '../models/user'; + +describe('Actions for User', () => { + it('LoadUserSuccess type is UserActionTypes.LOAD_USER_SUCCESS', () => { + const user: User = { + name: 'Jerson Morocho', + email: 'jerson.morocho@ioet.com', + roles: [], + groups: [], + id: 'dd4a1571-b025-41c9-b35f-810841b43134', + tenant_id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + deleted: '' + }; + + const loadUserSuccess = new LoadUserSuccess(user); + + expect(loadUserSuccess.type).toEqual(UserActionTypes.LOAD_USER_SUCCESS); + }); + + it('LoadUserFail type is UserActionTypes.LOAD_USER_FAIL', () => { + const loadUserFail = new LoadUserFail('error'); + + expect(loadUserFail.type).toEqual(UserActionTypes.LOAD_USER_FAIL); + }); + +}); diff --git a/src/app/modules/user/store/user.effects.spec.ts b/src/app/modules/user/store/user.effects.spec.ts index e69de29bb..28adef5a2 100644 --- a/src/app/modules/user/store/user.effects.spec.ts +++ b/src/app/modules/user/store/user.effects.spec.ts @@ -0,0 +1,62 @@ +import { Observable, of, throwError } from 'rxjs'; +import { Action } from '@ngrx/store'; +import { User } from '../models/user'; +import { UserEffects } from './user.effects'; +import { TestBed } from '@angular/core/testing'; +import { UserService } from '../services/user.service'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { LoadUser, UserActionTypes } from './user.actions'; + +describe('UserEffects', () => { + let actions$: Observable; + let effects: UserEffects; + let service: UserService; + const userInfo: User = { + name: 'Jerson Morocho', + email: 'jerson.morocho@ioet.com', + roles: [], + groups: [], + id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + tenant_id: null, + deleted: null + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [UserEffects, provideMockActions(() => actions$)], + imports: [HttpClientTestingModule], + }); + + effects = TestBed.inject(UserEffects); + service = TestBed.inject(UserService); + }); + + it('should be created', async () => { + expect(effects).toBeTruthy(); + }); + + it('action type is LOAD_USER_SUCCESS when service is executed successfully', async () => { + const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const serviceSpy = spyOn(service, 'loadUser'); + + actions$ = of(new LoadUser(userId)); + serviceSpy.and.returnValue(of(userInfo)); + + effects.loadUserInfo$.subscribe((action) => { + expect(action.type).toEqual(UserActionTypes.LOAD_USER_SUCCESS); + }); + }); + + it('action type is LOAD_USER_FAIL when service fail in execution', async () => { + const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const serviceSpy = spyOn(service, 'loadUser'); + + actions$ = of(new LoadUser(userId)); + serviceSpy.and.returnValue(throwError({ error: { message: 'fail!' } })); + + effects.loadUserInfo$.subscribe((action) => { + expect(action.type).toEqual(UserActionTypes.LOAD_USER_FAIL); + }); + }); +}); diff --git a/src/app/modules/user/store/user.effects.ts b/src/app/modules/user/store/user.effects.ts index 4322699a2..4533a5cc0 100644 --- a/src/app/modules/user/store/user.effects.ts +++ b/src/app/modules/user/store/user.effects.ts @@ -3,7 +3,7 @@ import { Actions, ofType, Effect } from '@ngrx/effects'; import { Action } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; -import { UserService } from './user.service'; +import { UserService } from '../services/user.service'; import * as actions from './user.actions'; @Injectable() diff --git a/src/app/modules/user/store/user.reducer.spec.ts b/src/app/modules/user/store/user.reducer.spec.ts index e69de29bb..0a12a3b07 100644 --- a/src/app/modules/user/store/user.reducer.spec.ts +++ b/src/app/modules/user/store/user.reducer.spec.ts @@ -0,0 +1,44 @@ +import { userReducer } from './user.reducer'; +import { LoadUser, LoadUserFail, LoadUserSuccess } from './user.actions'; +import { User } from '../models/user'; + +describe('userReducer', () => { + const initState = { + name: '', + email: '', + roles: [], + groups: [], + } + + it('on LoadUser, state equal to initState', () => { + const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const action = new LoadUser(userId); + const state = userReducer(initState, action); + + expect(state).toEqual(initState); + }); + + it('on LoadUserSuccess, userFound is saved in store', () => { + const userFound: User = { + name: 'Jerson Morocho', + email: 'jerson.morocho@ioet.com', + roles: [], + groups: [], + id: 'dd4a1571-b025-41c9-b35f-810841b43134', + tenant_id: null, + deleted: null + }; + + const action = new LoadUserSuccess(userFound); + const state = userReducer(initState, action); + + expect(state).toEqual(userFound); + }); + + it('on LoadUserFail, state equal to initState', () => { + const action = new LoadUserFail('error'); + const state = userReducer(initState, action); + + expect(state).toEqual(initState); + }); +}); diff --git a/src/app/modules/user/store/user.selectors.spec.ts b/src/app/modules/user/store/user.selectors.spec.ts index e69de29bb..c3f578ef4 100644 --- a/src/app/modules/user/store/user.selectors.spec.ts +++ b/src/app/modules/user/store/user.selectors.spec.ts @@ -0,0 +1,21 @@ +import { getUserInfo } from './user.selectors'; +import { User } from '../models/user'; + +describe('UserSelectors', () => { + const userInfo: User = { + name: 'Jerson Morocho', + email: 'jerson.morocho@ioet.com', + roles: [], + groups: [], + id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + tenant_id: null, + deleted: null + }; + + it('should select user info', () => { + const result = getUserInfo.projector(userInfo); + + expect(userInfo.email).toEqual('jerson.morocho@ioet.com'); + }); + +}); diff --git a/src/app/modules/user/store/user.service.ts b/src/app/modules/user/store/user.service.ts deleted file mode 100644 index d77c64906..000000000 --- a/src/app/modules/user/store/user.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; - -import { Observable } from 'rxjs'; -import { environment } from 'src/environments/environment'; - -@Injectable({ - providedIn: 'root', -}) -export class UserService { - - constructor(private http: HttpClient) { - } - - baseUrl = `${environment.timeTrackerApiUrl}/users`; - - loadUser(userId): Observable { - return this.http.get(`${this.baseUrl}/${userId}`); - } - -} From f4359a730f0e695be9efe14db43364c6c1cbab57 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Mon, 15 Mar 2021 16:21:26 -0500 Subject: [PATCH 5/9] feat: TT-155 create a service to verifyGroup in user --- .../components/sidebar/sidebar.component.ts | 6 +++++ .../user/services/user-info.service.spec.ts | 22 +++++++++++++++++++ .../user/services/user-info.service.ts | 21 ++++++++++++++++++ src/app/modules/user/services/user.service.ts | 21 ++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 src/app/modules/user/services/user-info.service.spec.ts create mode 100644 src/app/modules/user/services/user-info.service.ts create mode 100644 src/app/modules/user/services/user.service.ts diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.ts b/src/app/modules/shared/components/sidebar/sidebar.component.ts index 2d41223f9..e044948c6 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.ts +++ b/src/app/modules/shared/components/sidebar/sidebar.component.ts @@ -5,6 +5,7 @@ import {NavigationStart, Router} from '@angular/router'; import {Observable} from 'rxjs'; import {filter} from 'rxjs/operators'; import { FeatureManagerService } from '../../feature-toggles/feature-toggle-manager.service'; +import { Store } from '@ngrx/store'; @Component({ selector: 'app-sidebar', @@ -20,6 +21,8 @@ export class SidebarComponent implements OnInit { private azureAdB2CService: AzureAdB2CService, private router: Router, private featureManagerService: FeatureManagerService, + private store: Store, + private bc2azure: AzureAdB2CService, ) { this.navStart = this.router.events.pipe( filter(evt => evt instanceof NavigationStart) @@ -34,6 +37,9 @@ export class SidebarComponent implements OnInit { this.navStart.subscribe(evt => { this.highlightMenuOption(evt.url); }); + + console.log(this.bc2azure.getUserId()); + } toggleSideBar() { diff --git a/src/app/modules/user/services/user-info.service.spec.ts b/src/app/modules/user/services/user-info.service.spec.ts new file mode 100644 index 000000000..ecdd56f82 --- /dev/null +++ b/src/app/modules/user/services/user-info.service.spec.ts @@ -0,0 +1,22 @@ +import { TestBed } from '@angular/core/testing'; + +import { UserInfoService } from './user-info.service'; + +describe('UserInfoService', () => { + let service: UserInfoService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(UserInfoService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should verify if an user belongs to a certain group in the UI', () => { + const input = 'time-tracker-admin'; + + expect(service.verifyGroup(input)).toBeTruthy(); + }); +}); diff --git a/src/app/modules/user/services/user-info.service.ts b/src/app/modules/user/services/user-info.service.ts new file mode 100644 index 000000000..112902a86 --- /dev/null +++ b/src/app/modules/user/services/user-info.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { getUserGroupsInfo } from '../store/user.selectors'; + +@Injectable({ + providedIn: 'root' +}) +export class UserInfoService { + + constructor(private store: Store) { } + + verifyGroup(groupName: string): boolean { + let isGroupBelongsToUser: boolean; + + const groupsStored = this.store.pipe(select(getUserGroupsInfo)).subscribe((groups) => { + isGroupBelongsToUser = groups.includes(groupName); + }); + + return isGroupBelongsToUser; + } +} diff --git a/src/app/modules/user/services/user.service.ts b/src/app/modules/user/services/user.service.ts new file mode 100644 index 000000000..d77c64906 --- /dev/null +++ b/src/app/modules/user/services/user.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class UserService { + + constructor(private http: HttpClient) { + } + + baseUrl = `${environment.timeTrackerApiUrl}/users`; + + loadUser(userId): Observable { + return this.http.get(`${this.baseUrl}/${userId}`); + } + +} From 2e6728490cc096d6b59ffcabddbb9a2d6f922d20 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Mon, 15 Mar 2021 17:53:44 -0500 Subject: [PATCH 6/9] test: TT-155 modify test input data --- src/app/modules/user/store/user.actions.spec.ts | 8 ++++---- src/app/modules/user/store/user.effects.spec.ts | 10 +++++----- src/app/modules/user/store/user.reducer.spec.ts | 8 ++++---- src/app/modules/user/store/user.selectors.spec.ts | 8 ++++---- src/app/modules/user/store/user.selectors.ts | 4 +++- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/app/modules/user/store/user.actions.spec.ts b/src/app/modules/user/store/user.actions.spec.ts index bcae1136a..99c2746ad 100644 --- a/src/app/modules/user/store/user.actions.spec.ts +++ b/src/app/modules/user/store/user.actions.spec.ts @@ -5,12 +5,12 @@ import { User } from '../models/user'; describe('Actions for User', () => { it('LoadUserSuccess type is UserActionTypes.LOAD_USER_SUCCESS', () => { const user: User = { - name: 'Jerson Morocho', - email: 'jerson.morocho@ioet.com', + name: 'Unknown Name', + email: 'example@mail.com', roles: [], groups: [], - id: 'dd4a1571-b025-41c9-b35f-810841b43134', - tenant_id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + id: 'dummy_id_load', + tenant_id: 'dummy_tenant_id_load', deleted: '' }; diff --git a/src/app/modules/user/store/user.effects.spec.ts b/src/app/modules/user/store/user.effects.spec.ts index 28adef5a2..c2ec5c2a3 100644 --- a/src/app/modules/user/store/user.effects.spec.ts +++ b/src/app/modules/user/store/user.effects.spec.ts @@ -13,11 +13,11 @@ describe('UserEffects', () => { let effects: UserEffects; let service: UserService; const userInfo: User = { - name: 'Jerson Morocho', - email: 'jerson.morocho@ioet.com', + name: 'Unknown Name', + email: 'example@mail.com', roles: [], groups: [], - id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + id: 'dummy_tenant_id_load', tenant_id: null, deleted: null }; @@ -37,7 +37,7 @@ describe('UserEffects', () => { }); it('action type is LOAD_USER_SUCCESS when service is executed successfully', async () => { - const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const userId = 'dummy_id_load'; const serviceSpy = spyOn(service, 'loadUser'); actions$ = of(new LoadUser(userId)); @@ -49,7 +49,7 @@ describe('UserEffects', () => { }); it('action type is LOAD_USER_FAIL when service fail in execution', async () => { - const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const userId = 'dummy_id_load'; const serviceSpy = spyOn(service, 'loadUser'); actions$ = of(new LoadUser(userId)); diff --git a/src/app/modules/user/store/user.reducer.spec.ts b/src/app/modules/user/store/user.reducer.spec.ts index 0a12a3b07..8c6d573dd 100644 --- a/src/app/modules/user/store/user.reducer.spec.ts +++ b/src/app/modules/user/store/user.reducer.spec.ts @@ -11,7 +11,7 @@ describe('userReducer', () => { } it('on LoadUser, state equal to initState', () => { - const userId = 'dd4a1571-b025-41c9-b35f-810841b43134'; + const userId = 'dummy_id_load'; const action = new LoadUser(userId); const state = userReducer(initState, action); @@ -20,11 +20,11 @@ describe('userReducer', () => { it('on LoadUserSuccess, userFound is saved in store', () => { const userFound: User = { - name: 'Jerson Morocho', - email: 'jerson.morocho@ioet.com', + name: 'Unknown Name', + email: 'example@mail.com', roles: [], groups: [], - id: 'dd4a1571-b025-41c9-b35f-810841b43134', + id: 'dummy_id_load', tenant_id: null, deleted: null }; diff --git a/src/app/modules/user/store/user.selectors.spec.ts b/src/app/modules/user/store/user.selectors.spec.ts index c3f578ef4..356b050df 100644 --- a/src/app/modules/user/store/user.selectors.spec.ts +++ b/src/app/modules/user/store/user.selectors.spec.ts @@ -3,11 +3,11 @@ import { User } from '../models/user'; describe('UserSelectors', () => { const userInfo: User = { - name: 'Jerson Morocho', - email: 'jerson.morocho@ioet.com', + name: 'Unknown Name', + email: 'example@mail.com', roles: [], groups: [], - id: 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + id: 'dummy_tenant_id_load', tenant_id: null, deleted: null }; @@ -15,7 +15,7 @@ describe('UserSelectors', () => { it('should select user info', () => { const result = getUserInfo.projector(userInfo); - expect(userInfo.email).toEqual('jerson.morocho@ioet.com'); + expect(userInfo.email).toEqual('example@mail.com'); }); }); diff --git a/src/app/modules/user/store/user.selectors.ts b/src/app/modules/user/store/user.selectors.ts index 1d0cc857f..525d288fe 100644 --- a/src/app/modules/user/store/user.selectors.ts +++ b/src/app/modules/user/store/user.selectors.ts @@ -1,5 +1,7 @@ import { createFeatureSelector, createSelector } from '@ngrx/store'; +import { User } from '../models/user'; const getUserState = createFeatureSelector('user'); -export const getUserInfo = createSelector(getUserState, (state: any) => state); +export const getUserInfo = createSelector(getUserState, (state: User) => state); +export const getUserGroups = createSelector(getUserState, (state: User) => state.groups); From d6b84b200d7969cd9eb311d22c40859e91ff6174 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Thu, 18 Mar 2021 21:13:56 -0500 Subject: [PATCH 7/9] test: TT-155 added unit tests to user-info.service --- .../user/services/user-info.service.spec.ts | 48 +++++++++++++++++-- .../user/services/user-info.service.ts | 30 ++++++++---- src/environments/environment.ts | 4 ++ 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/app/modules/user/services/user-info.service.spec.ts b/src/app/modules/user/services/user-info.service.spec.ts index ecdd56f82..33fddfd45 100644 --- a/src/app/modules/user/services/user-info.service.spec.ts +++ b/src/app/modules/user/services/user-info.service.spec.ts @@ -1,22 +1,60 @@ import { TestBed } from '@angular/core/testing'; - +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { getUserGroups } from '../store/user.selectors'; import { UserInfoService } from './user-info.service'; describe('UserInfoService', () => { let service: UserInfoService; + let store: MockStore; + let mockGetUserGroupsSelector: any; + const initialState = { + name: 'Unknown Name', + email: 'example@mail.com', + roles: [], + groups: ['fake-admin', 'fake-tester'], + id: 'dummy_id_load', + tenant_id: 'dummy_tenant_id_load', + deleted: '', + }; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [provideMockStore({ initialState })], + }); service = TestBed.inject(UserInfoService); + store = TestBed.inject(MockStore); + mockGetUserGroupsSelector = store.overrideSelector(getUserGroups, initialState.groups); }); it('should be created', () => { expect(service).toBeTruthy(); }); - it('should verify if an user belongs to a certain group in the UI', () => { - const input = 'time-tracker-admin'; + it('should call groups selector', () => { + const expectedGroups = ['fake-admin', 'fake-tester']; + + service.groups().subscribe((value) => { + expect(value).toEqual(expectedGroups); + }); + }); + + const params = [ + { groupName: 'fake-admin', expectedValue: true, groups: ['fake-admin', 'fake-tester'] }, + { groupName: 'fake-owner', expectedValue: false, groups: ['fake-admin', 'fake-tester'] }, + ]; + + params.map((param) => { + it(`given group ${param.groupName} and groups [${param.groups.toString()}], isMemberOf() should return ${ + param.expectedValue + }`, () => { + const groups$ = of(param.groups); + + spyOn(service, 'groups').and.returnValue(groups$); - expect(service.verifyGroup(input)).toBeTruthy(); + service.isMemberOf(param.groupName).subscribe((value) => { + expect(value).toEqual(param.expectedValue); + }); + }); }); }); diff --git a/src/app/modules/user/services/user-info.service.ts b/src/app/modules/user/services/user-info.service.ts index 112902a86..a9e3e5161 100644 --- a/src/app/modules/user/services/user-info.service.ts +++ b/src/app/modules/user/services/user-info.service.ts @@ -1,21 +1,33 @@ import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { getUserGroupsInfo } from '../store/user.selectors'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { getUserGroups } from '../store/user.selectors'; +import { GROUPS } from '../../../../environments/environment'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class UserInfoService { + constructor(private store: Store) {} - constructor(private store: Store) { } + groups(): Observable { + return this.store.pipe(select(getUserGroups)); + } - verifyGroup(groupName: string): boolean { - let isGroupBelongsToUser: boolean; + isMemberOf(groupName: string): Observable { + return this.groups().pipe( + map((groups: string[]) => { + return groups.includes(groupName); + }) + ); + } - const groupsStored = this.store.pipe(select(getUserGroupsInfo)).subscribe((groups) => { - isGroupBelongsToUser = groups.includes(groupName); - }); + isAdmin(): Observable { + return this.isMemberOf(GROUPS.ADMIN); + } - return isGroupBelongsToUser; + isTester(): Observable { + return this.isMemberOf(GROUPS.TESTER); } } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 208e0a880..5b04fe66e 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -18,6 +18,10 @@ export const STACK_EXCHANGE_ACCESS_TOKEN = keys.STACK_EXCHANGE_ACCESS_TOKEN; export const AZURE_APP_CONFIGURATION_CONNECTION_STRING = keys.AZURE_APP_CONFIGURATION_CONNECTION_STRING; export const DATE_FORMAT = 'yyyy-MM-dd'; export const DATE_FORMAT_YEAR = 'YYYY-MM-DD'; +export const GROUPS = { + ADMIN: 'time-tracker-admin', + TESTER: 'time-tracker-tester', +}; /* * For easier debugging in development mode, you can import the following file From 29e1b27dab6430b13b0c1f050edc8a3f663662a8 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Fri, 19 Mar 2021 10:45:43 -0500 Subject: [PATCH 8/9] test: TT-155 deleted two extra lines in sidebar --- .../modules/shared/components/sidebar/sidebar.component.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.ts b/src/app/modules/shared/components/sidebar/sidebar.component.ts index e044948c6..2d41223f9 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.ts +++ b/src/app/modules/shared/components/sidebar/sidebar.component.ts @@ -5,7 +5,6 @@ import {NavigationStart, Router} from '@angular/router'; import {Observable} from 'rxjs'; import {filter} from 'rxjs/operators'; import { FeatureManagerService } from '../../feature-toggles/feature-toggle-manager.service'; -import { Store } from '@ngrx/store'; @Component({ selector: 'app-sidebar', @@ -21,8 +20,6 @@ export class SidebarComponent implements OnInit { private azureAdB2CService: AzureAdB2CService, private router: Router, private featureManagerService: FeatureManagerService, - private store: Store, - private bc2azure: AzureAdB2CService, ) { this.navStart = this.router.events.pipe( filter(evt => evt instanceof NavigationStart) @@ -37,9 +34,6 @@ export class SidebarComponent implements OnInit { this.navStart.subscribe(evt => { this.highlightMenuOption(evt.url); }); - - console.log(this.bc2azure.getUserId()); - } toggleSideBar() { From 872952eee59440b910a96d60cd26cdd1ccaf7339 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Fri, 19 Mar 2021 15:29:09 -0500 Subject: [PATCH 9/9] fix: TT-155 resolve comments & fix user.selectors.spec --- .gitignore | 1 + debug.log | 8 ----- .../shared/components/user/user.component.ts | 1 - src/app/modules/user/models/user.ts | 2 +- src/app/modules/user/services/user.service.ts | 7 ++--- .../modules/user/store/user.reducer.spec.ts | 18 ++++++------ .../modules/user/store/user.selectors.spec.ts | 29 ++++++++++--------- 7 files changed, 29 insertions(+), 37 deletions(-) delete mode 100644 debug.log diff --git a/.gitignore b/.gitignore index 71b3b824f..22cd5e848 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ testem.log .keys.json keys.ts src/environments/keys.ts +debug.log # System Files .DS_Store diff --git a/debug.log b/debug.log deleted file mode 100644 index 04d6ae9ee..000000000 --- a/debug.log +++ /dev/null @@ -1,8 +0,0 @@ -[1207/120956.471:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1209/102017.100:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1209/114251.202:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1211/010054.703:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1211/165324.600:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1214/163635.420:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1218/134332.967:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) -[1218/155414.096:ERROR:directory_reader_win.cc(43)] FindFirstFile: El sistema no puede encontrar la ruta especificada. (0x3) diff --git a/src/app/modules/shared/components/user/user.component.ts b/src/app/modules/shared/components/user/user.component.ts index 798c8e0c2..5f99f7399 100644 --- a/src/app/modules/shared/components/user/user.component.ts +++ b/src/app/modules/shared/components/user/user.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service'; - @Component({ selector: 'app-user', templateUrl: './user.component.html', diff --git a/src/app/modules/user/models/user.ts b/src/app/modules/user/models/user.ts index 750a95d86..604b84970 100644 --- a/src/app/modules/user/models/user.ts +++ b/src/app/modules/user/models/user.ts @@ -1,7 +1,7 @@ export interface User { name: string; email: string; - roles?: string[]; + roles?: string[]; groups?: string[]; id: string; tenant_id?: string; diff --git a/src/app/modules/user/services/user.service.ts b/src/app/modules/user/services/user.service.ts index d77c64906..ac711ad4d 100644 --- a/src/app/modules/user/services/user.service.ts +++ b/src/app/modules/user/services/user.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; - import { Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; +import { User } from '../models/user'; @Injectable({ providedIn: 'root', @@ -14,8 +14,7 @@ export class UserService { baseUrl = `${environment.timeTrackerApiUrl}/users`; - loadUser(userId): Observable { - return this.http.get(`${this.baseUrl}/${userId}`); + loadUser(userId: string): Observable { + return this.http.get(`${this.baseUrl}/${userId}`); } - } diff --git a/src/app/modules/user/store/user.reducer.spec.ts b/src/app/modules/user/store/user.reducer.spec.ts index 8c6d573dd..a73313362 100644 --- a/src/app/modules/user/store/user.reducer.spec.ts +++ b/src/app/modules/user/store/user.reducer.spec.ts @@ -3,19 +3,19 @@ import { LoadUser, LoadUserFail, LoadUserSuccess } from './user.actions'; import { User } from '../models/user'; describe('userReducer', () => { - const initState = { + const initialState = { name: '', email: '', roles: [], groups: [], - } + }; - it('on LoadUser, state equal to initState', () => { + it('on LoadUser, state equal to initialState', () => { const userId = 'dummy_id_load'; const action = new LoadUser(userId); - const state = userReducer(initState, action); + const state = userReducer(initialState, action); - expect(state).toEqual(initState); + expect(state).toEqual(initialState); }); it('on LoadUserSuccess, userFound is saved in store', () => { @@ -30,15 +30,15 @@ describe('userReducer', () => { }; const action = new LoadUserSuccess(userFound); - const state = userReducer(initState, action); + const state = userReducer(initialState, action); expect(state).toEqual(userFound); }); - it('on LoadUserFail, state equal to initState', () => { + it('on LoadUserFail, state equal to initialState', () => { const action = new LoadUserFail('error'); - const state = userReducer(initState, action); + const state = userReducer(initialState, action); - expect(state).toEqual(initState); + expect(state).toEqual(initialState); }); }); diff --git a/src/app/modules/user/store/user.selectors.spec.ts b/src/app/modules/user/store/user.selectors.spec.ts index 356b050df..d5fc7e103 100644 --- a/src/app/modules/user/store/user.selectors.spec.ts +++ b/src/app/modules/user/store/user.selectors.spec.ts @@ -1,21 +1,22 @@ -import { getUserInfo } from './user.selectors'; +import { getUserGroups, getUserInfo } from './user.selectors'; import { User } from '../models/user'; describe('UserSelectors', () => { - const userInfo: User = { - name: 'Unknown Name', - email: 'example@mail.com', - roles: [], - groups: [], - id: 'dummy_tenant_id_load', - tenant_id: null, - deleted: null - }; + const userState: User = { + name: 'Unknown Name', + email: 'example@mail.com', + roles: [], + groups: [], + id: 'dummy_tenant_id_load', + tenant_id: null, + deleted: null, + }; - it('should select user info', () => { - const result = getUserInfo.projector(userInfo); - - expect(userInfo.email).toEqual('example@mail.com'); + it('should select user from store', () => { + expect(getUserInfo.projector(userState)).toEqual(userState); }); + it('should select user groups from store', () => { + expect(getUserGroups.projector(userState)).toEqual(userState.groups); + }); });