Skip to content

Commit f930cfe

Browse files
authored
Merge branch 'master' into TT-20-Calculate-time-difference-when-new-entries-added
2 parents e2f7444 + f0c2c11 commit f930cfe

28 files changed

+809
-457
lines changed

package-lock.json

Lines changed: 260 additions & 282 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "time-tracker",
3-
"version": "1.35.0",
3+
"version": "1.38.0",
44
"scripts": {
55
"preinstall": "npx npm-force-resolutions",
66
"ng": "ng",
@@ -42,6 +42,7 @@
4242
"minimist": "^1.2.5",
4343
"moment": "^2.25.3",
4444
"msal": "^1.2.1",
45+
"ngrx-store-localstorage": "^11.0.0",
4546
"ngx-cookie-service": "^11.0.2",
4647
"ngx-mask": "^9.1.2",
4748
"ngx-material-timepicker": "^5.5.3",
@@ -88,7 +89,7 @@
8889
"popper.js": "^1.16.0",
8990
"prettier": "^2.0.2",
9091
"protractor": "^7.0.0",
91-
"semantic-release": "^17.3.0",
92+
"semantic-release": "^17.4.2",
9293
"ts-node": "~8.3.0",
9394
"tslint": "~6.1.0",
9495
"typescript": "4.0.5"

src/app/app-routing.module.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AdminGuard } from './guards/admin-guard/admin-guard';
1+
import { AdminGuard } from './guards/admin-guard/admin.guard';
22
import { NgModule } from '@angular/core';
33
import { Routes, RouterModule } from '@angular/router';
44

@@ -26,7 +26,7 @@ const routes: Routes = [
2626
{ path: 'activities-management', component: ActivitiesManagementComponent },
2727
{ path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent },
2828
{ path: 'users', canActivate: [AdminGuard], component: UsersComponent },
29-
{ path: 'technology-report', canActivate: [AdminGuard, TechnologiesReportGuard], component: TechnologyReportComponent},
29+
{ path: 'technology-report', canActivate: [AdminGuard, TechnologiesReportGuard], component: TechnologyReportComponent },
3030
{ path: '', pathMatch: 'full', redirectTo: 'time-clock' },
3131
],
3232
},
@@ -37,4 +37,4 @@ const routes: Routes = [
3737
imports: [RouterModule.forRoot(routes)],
3838
exports: [RouterModule],
3939
})
40-
export class AppRoutingModule {}
40+
export class AppRoutingModule { }

src/app/guards/admin-guard/admin-guard.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,129 @@
11
import { inject, TestBed } from '@angular/core/testing';
22
import { Router } from '@angular/router';
33
import { RouterTestingModule } from '@angular/router/testing';
4-
4+
import { of } from 'rxjs';
5+
import { skip, take } from 'rxjs/operators';
6+
import { FeatureSwitchGroupService } from 'src/app/modules/shared/feature-toggles/switch-group/feature-switch-group.service';
7+
import { UserInfoService } from 'src/app/modules/user/services/user-info.service';
58
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
6-
import { AdminGuard } from './admin-guard';
9+
import { AdminGuard } from './admin.guard';
710

811
describe('AdminGuard', () => {
9-
1012
let adminGuard: AdminGuard;
1113
let azureAdB2CService: AzureAdB2CService;
12-
14+
let userInfoService: UserInfoService;
15+
let featureSwitchGroupService: FeatureSwitchGroupService;
1316
const azureAdB2CServiceStub = {
1417
isLogin() {
1518
return true;
1619
},
1720
isAdmin() {
1821
return true;
19-
}
22+
},
23+
};
24+
25+
const userInfoServiceStub = {
26+
isAdmin: () => of(false),
27+
};
28+
29+
const featureSwitchGroupServiceStub = {
30+
isActivated: () => of(false),
2031
};
2132

2233
beforeEach(() => {
2334
TestBed.configureTestingModule({
2435
imports: [RouterTestingModule],
2536
providers: [
26-
{ providers: AzureAdB2CService, useValue: azureAdB2CServiceStub },
27-
]
37+
{ provide: AzureAdB2CService, useValue: azureAdB2CServiceStub },
38+
{ provide: UserInfoService, useValue: userInfoServiceStub },
39+
{ provide: FeatureSwitchGroupService, useValue: featureSwitchGroupServiceStub },
40+
],
2841
});
2942
adminGuard = TestBed.inject(AdminGuard);
3043
azureAdB2CService = TestBed.inject(AzureAdB2CService);
44+
userInfoService = TestBed.inject(UserInfoService);
45+
featureSwitchGroupService = TestBed.inject(FeatureSwitchGroupService);
3146
});
3247

3348
it('should be created', () => {
3449
expect(adminGuard).toBeTruthy();
3550
});
3651

37-
it('can activate the route when user is logged-in', () => {
38-
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(true);
52+
const roleParams = [{ bool: false }, { bool: true }];
53+
roleParams.map((param) => {
54+
it(`isAdminBasedInRole return ${param.bool}`, () => {
55+
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(param.bool);
56+
57+
adminGuard.isAdminBasedInRole().subscribe((enabled) => {
58+
expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
59+
expect(enabled).toBe(param.bool);
60+
});
61+
});
62+
});
63+
64+
const groupParams = [{ bool: false }, { bool: true }];
65+
groupParams.map((param) => {
66+
it(`isAdminBasedInGroup return ${param.bool}`, () => {
67+
spyOn(userInfoService, 'isAdmin').and.returnValue(of(param.bool));
68+
69+
adminGuard.isAdminBasedInGroup().subscribe((enabled) => {
70+
expect(userInfoService.isAdmin).toHaveBeenCalled();
71+
expect(enabled).toBe(param.bool);
72+
});
73+
});
74+
});
75+
76+
const switchToggleParams = [
77+
{ switchGroup: false, chosen: 'isAdminBasedInRole', isAdmin: true },
78+
{ switchGroup: true, chosen: 'isAdminBasedInGroup', isAdmin: false },
79+
];
80+
switchToggleParams.map((param) => {
81+
it(`on switchGroup ${param.switchGroup}, ${param.chosen} should be chosen`, () => {
82+
const switchGroup$ = of(param.switchGroup);
83+
84+
spyOn(featureSwitchGroupService, 'isActivated').and.returnValue(switchGroup$);
85+
86+
const canActivate = adminGuard.canActivate();
3987

40-
const canActivate = adminGuard.canActivate();
88+
featureSwitchGroupService.isActivated().pipe(take(1));
4189

42-
expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
43-
expect(canActivate).toEqual(true);
90+
canActivate.subscribe((enabled) => {
91+
expect(featureSwitchGroupService.isActivated).toHaveBeenCalled();
92+
expect(enabled).toBe(param.isAdmin);
93+
});
94+
});
4495
});
4596

46-
it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => {
47-
spyOn(azureAdB2CService, 'isAdmin').and.returnValue(false);
48-
spyOn(router, 'navigate').and.stub();
97+
const navigateParams = [
98+
{ switchGroup: false, chosen: 'activate the route', isAdmin: true },
99+
{ switchGroup: false, chosen: 'redirect to /login', isAdmin: false },
100+
{ switchGroup: true, chosen: 'activate the route', isAdmin: true },
101+
{ switchGroup: true, chosen: 'redirect to /login', isAdmin: false },
102+
];
103+
navigateParams.map((param) => {
104+
it(`on isAdmin: ${param.isAdmin} with toggleSwitch: ${param.switchGroup}, should ${param.chosen} `, inject(
105+
[Router],
106+
(router: Router) => {
107+
const switchGroup$ = of(param.switchGroup);
108+
const isAdmin$ = of(param.isAdmin);
49109

50-
const canActivate = adminGuard.canActivate();
110+
spyOn(featureSwitchGroupService, 'isActivated').and.returnValue(switchGroup$);
111+
spyOn(adminGuard, 'isAdminBasedInRole').and.returnValue(isAdmin$);
112+
spyOn(adminGuard, 'isAdminBasedInGroup').and.returnValue(isAdmin$);
113+
spyOn(router, 'navigate').and.stub();
51114

52-
expect(azureAdB2CService.isAdmin).toHaveBeenCalled();
53-
expect(canActivate).toEqual(false);
54-
expect(router.navigate).toHaveBeenCalledWith(['login']);
55-
}));
115+
const canActivate = adminGuard.canActivate();
56116

117+
canActivate.subscribe((enabled) => {
118+
expect(featureSwitchGroupService.isActivated).toHaveBeenCalled();
119+
if (!enabled) {
120+
expect(router.navigate).toHaveBeenCalledWith(['login']);
121+
} else {
122+
expect(router.navigate).not.toHaveBeenCalled();
123+
expect(enabled).toBeTrue();
124+
}
125+
});
126+
}
127+
));
128+
});
57129
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Injectable } from '@angular/core';
2+
import { Router, CanActivate } from '@angular/router';
3+
import { Observable, of } from 'rxjs';
4+
import { map, mergeMap } from 'rxjs/operators';
5+
import { FeatureSwitchGroupService } from 'src/app/modules/shared/feature-toggles/switch-group/feature-switch-group.service';
6+
import { UserInfoService } from 'src/app/modules/user/services/user-info.service';
7+
import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service';
8+
9+
@Injectable({
10+
providedIn: 'root',
11+
})
12+
export class AdminGuard implements CanActivate {
13+
constructor(
14+
private azureAdB2CService: AzureAdB2CService,
15+
private router: Router,
16+
private userInfoService: UserInfoService,
17+
private featureSwitchGroup: FeatureSwitchGroupService
18+
) {}
19+
20+
canActivate(): Observable<boolean> {
21+
return this.featureSwitchGroup.isActivated().pipe(
22+
mergeMap((enabled: boolean) => {
23+
return enabled ? this.isAdminBasedInGroup() : this.isAdminBasedInRole();
24+
}),
25+
map((isAdmin: boolean): boolean => {
26+
if (!isAdmin) {
27+
this.router.navigate(['login']);
28+
}
29+
return isAdmin;
30+
})
31+
);
32+
}
33+
34+
isAdminBasedInRole(): Observable<boolean> {
35+
return of(this.azureAdB2CService.isAdmin());
36+
}
37+
38+
isAdminBasedInGroup(): Observable<boolean> {
39+
return this.userInfoService.isAdmin();
40+
}
41+
}

src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
} from 'src/app/modules/customer-management/store';
1313
import { DataTablesModule } from 'angular-datatables';
1414
import { ActionsSubject } from '@ngrx/store';
15-
import { ResetProjectToEdit } from '../../../projects/components/store/project.actions';
16-
import { ResetProjectTypeToEdit } from '../../../projects-type/store';
15+
import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/components/store/project.actions';
16+
import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store';
1717

1818
describe('CustomerTableListComponent', () => {
1919
let component: CustomerListComponent;
@@ -58,7 +58,6 @@ describe('CustomerTableListComponent', () => {
5858
expect(store.dispatch).toHaveBeenCalledWith(new LoadCustomers());
5959
});
6060

61-
6261
it('Onclick Edit, if there are changes, the modal must be presented ', () => {
6362
component.hasChange = true;
6463
const expectMessage = 'Do you have changes in a client, do you want to discard them?';
@@ -86,6 +85,8 @@ describe('CustomerTableListComponent', () => {
8685

8786
component.editCustomer('1');
8887

88+
expect(store.dispatch).toHaveBeenCalledWith(new SetProjectToEdit(null));
89+
expect(store.dispatch).toHaveBeenCalledWith(new SetProjectTypeToEdit(null));
8990
expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectToEdit());
9091
expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectTypeToEdit());
9192
});

src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import {
1111
LoadCustomers,
1212
SetCustomerToEdit,
1313
} from './../../../../store/customer-management.actions';
14-
import { ResetProjectToEdit } from '../../../projects/components/store/project.actions';
15-
import { ResetProjectTypeToEdit } from '../../../projects-type/store';
14+
import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/components/store/project.actions';
15+
import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store';
16+
1617
@Component({
1718
selector: 'app-customer-list',
1819
templateUrl: './customer-list.component.html',
@@ -101,6 +102,8 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit {
101102
}
102103

103104
private resetProjectFieldsToEdit() {
105+
this.store.dispatch(new SetProjectTypeToEdit(null));
106+
this.store.dispatch(new SetProjectToEdit(null));
104107
this.store.dispatch(new ResetProjectToEdit());
105108
this.store.dispatch(new ResetProjectTypeToEdit());
106109
}

src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
22
import { FormBuilder } from '@angular/forms';
33
import { provideMockStore, MockStore } from '@ngrx/store/testing';
4-
4+
import { ReactiveFormsModule } from '@angular/forms';
55
import { CreateProjectTypeComponent } from './create-project-type.component';
66
import {
77
ProjectTypeState,
@@ -40,6 +40,7 @@ describe('InputProjectTypeComponent', () => {
4040

4141
beforeEach(waitForAsync(() => {
4242
TestBed.configureTestingModule({
43+
imports: [ReactiveFormsModule],
4344
declarations: [CreateProjectTypeComponent],
4445
providers: [FormBuilder, provideMockStore({ initialState: state })],
4546
}).compileComponents();
@@ -121,6 +122,15 @@ describe('InputProjectTypeComponent', () => {
121122
expect(store.dispatch).toHaveBeenCalledWith(new CreateProjectType(projectTypeData));
122123
});
123124

125+
it('should reset projectTypeForm if projectTypeIdToEdit is null', () => {
126+
127+
spyOn(component.projectTypeForm, 'reset');
128+
129+
store.overrideSelector(projectTypeIdToEdit, null);
130+
store.refreshState();
131+
expect(component.projectTypeForm.reset).toHaveBeenCalled();
132+
});
133+
124134
it('should get name using projectTypeForm', () => {
125135
spyOn(component.projectTypeForm, 'get');
126136
// tslint:disable-next-line:no-unused-expression

src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FormBuilder, Validators, FormGroup } from '@angular/forms';
33
import { Store, select } from '@ngrx/store';
44

55
import { ProjectType } from '../../../../../shared/models';
6-
import { ProjectTypeState } from '../../store';
6+
import { projectTypeIdToEdit, ProjectTypeState } from '../../store';
77
import { CreateProjectType, ResetProjectTypeToEdit, UpdateProjectType, getProjectTypeById } from '../../store';
88
import { getCustomerId } from 'src/app/modules/customer-management/store/customer-management.selectors';
99
import { Subscription } from 'rxjs';
@@ -20,6 +20,7 @@ export class CreateProjectTypeComponent implements OnInit, OnDestroy {
2020
projectTypeToEdit: ProjectType;
2121
customerId: string;
2222
getCustomerIdSubscription: Subscription;
23+
projectTypeIdToEditSubscription: Subscription;
2324

2425
constructor(private formBuilder: FormBuilder, private store: Store<ProjectTypeState>) {
2526
this.projectTypeForm = this.formBuilder.group({
@@ -29,6 +30,13 @@ export class CreateProjectTypeComponent implements OnInit, OnDestroy {
2930
}
3031

3132
ngOnInit(): void {
33+
const projectTypeIdToEdit$ = this.store.pipe(select(projectTypeIdToEdit));
34+
this.projectTypeIdToEditSubscription = projectTypeIdToEdit$.subscribe((projectTypeId: string) => {
35+
if (projectTypeId === null) {
36+
this.projectTypeForm.reset();
37+
}
38+
});
39+
3240
const projectType$ = this.store.pipe(select(getProjectTypeById));
3341
projectType$.subscribe((projectType) => {
3442
this.projectTypeToEdit = projectType;
@@ -79,6 +87,7 @@ export class CreateProjectTypeComponent implements OnInit, OnDestroy {
7987

8088
ngOnDestroy(): void {
8189
this.getCustomerIdSubscription.unsubscribe();
90+
this.projectTypeIdToEditSubscription.unsubscribe();
8291
}
8392

8493
onInputChangeProjectType(searchValue: string): void {

0 commit comments

Comments
 (0)