Skip to content

Commit e54d1ad

Browse files
authored
fix: TT-310 Reports do not work with groups fixed (#720)
* fix: TT-310 Reports does not work with groups fixed * code-smell: TT-310 Fixed code smells indicated by sonarCloud * refactor: TT-310 Improve role implementation * fix: TT-310 Testing implementation fixed * test: TT-310 Add missing tests
1 parent 87b2053 commit e54d1ad

File tree

10 files changed

+442
-43
lines changed

10 files changed

+442
-43
lines changed

src/app/modules/users/components/users-list/users-list.component.html

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
class="table table-sm table-bordered table-striped mb-0"
55
datatable
66
[dtTrigger]="dtTrigger"
7-
[dtOptions]="dtOptions"
8-
>
7+
[dtOptions]="dtOptions">
98
<thead class="thead-blue">
109
<tr class="d-flex flex-wrap">
1110
<th class="col-4">User Email</th>
@@ -21,15 +20,13 @@
2120
<td class="col-3 text-center">
2221
<ui-switch
2322
size="small"
24-
(change)="switchGroup('time-tracker-admin', user)"
25-
[checked]="user.groups.includes('time-tracker-admin')"
26-
></ui-switch>
23+
(change)="switchGroup('time-tracker-admin', user); updateRole(ROLES.admin, user, $event);"
24+
[checked]="user.groups.includes('time-tracker-admin')"></ui-switch>
2725
admin
2826
<ui-switch
2927
size="small"
3028
(change)="switchGroup('time-tracker-tester', user)"
31-
[checked]="user.groups.includes('time-tracker-tester')"
32-
></ui-switch>
29+
[checked]="user.groups.includes('time-tracker-tester')"></ui-switch>
3330
test
3431
</td>
3532
</tr>

src/app/modules/users/components/users-list/users-list.component.spec.ts

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
22
import { MockStore, provideMockStore } from '@ngrx/store/testing';
33
import { NgxPaginationModule } from 'ngx-pagination';
44
import { UsersListComponent } from './users-list.component';
5-
import {
6-
UserActionTypes,
7-
UserState,
8-
LoadUsers,
9-
AddUserToGroup,
10-
RemoveUserFromGroup,
11-
} from '../../store';
5+
import { UserActionTypes, UserState, LoadUsers, AddUserToGroup, RemoveUserFromGroup } from '../../store';
126
import { ActionsSubject } from '@ngrx/store';
137
import { DataTablesModule } from 'angular-datatables';
8+
import { GrantUserRole, RevokeUserRole } from '../../store/user.actions';
9+
import { ROLES } from '../../../../../environments/environment';
1410

1511
describe('UsersListComponent', () => {
1612
let component: UsersListComponent;
@@ -39,10 +35,7 @@ describe('UsersListComponent', () => {
3935
TestBed.configureTestingModule({
4036
imports: [NgxPaginationModule, DataTablesModule],
4137
declarations: [UsersListComponent],
42-
providers: [
43-
provideMockStore({ initialState: state }),
44-
{ provide: ActionsSubject, useValue: actionSub },
45-
],
38+
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
4639
}).compileComponents();
4740
})
4841
);
@@ -100,10 +93,7 @@ describe('UsersListComponent', () => {
10093
});
10194
});
10295

103-
const AddGroupTypes = [
104-
{ groupName: 'time-tracker-admin' },
105-
{ groupName: 'time-tracker-tester' }
106-
];
96+
const AddGroupTypes = [{ groupName: 'time-tracker-admin' }, { groupName: 'time-tracker-tester' }];
10797

10898
AddGroupTypes.map((param) => {
10999
it(`When user switchGroup to ${param.groupName} and doesn't belong to any group, should add ${param.groupName} group to user`, () => {
@@ -179,6 +169,66 @@ describe('UsersListComponent', () => {
179169
expect(component.dtElement.dtInstance.then).toHaveBeenCalled();
180170
});
181171

172+
it('When the toggle is enabled, the role must be added to the user ', () => {
173+
const availableRoles = [
174+
{
175+
name: 'admin',
176+
value: 'time-tracker-admin',
177+
},
178+
{
179+
name: 'test',
180+
value: 'time-tracker-tester',
181+
},
182+
];
183+
184+
spyOn(store, 'dispatch');
185+
186+
const user = {
187+
id: 'no-matter-id',
188+
name: 'no-matter-name',
189+
email: 'no-matter-email',
190+
roles: [],
191+
};
192+
193+
availableRoles.forEach((role) => {
194+
const isToggleEnabled = true;
195+
component.updateRole(role, user, isToggleEnabled);
196+
expect(store.dispatch).toHaveBeenCalledWith(new GrantUserRole(user.id, role.name));
197+
});
198+
});
199+
200+
it('When the toggle is disabled, the role must be removed from the user', () => {
201+
const availableRoles = [
202+
{
203+
name: 'admin',
204+
value: 'time-tracker-admin',
205+
},
206+
{
207+
name: 'test',
208+
value: 'time-tracker-tester',
209+
},
210+
];
211+
212+
spyOn(store, 'dispatch');
213+
214+
const user = {
215+
id: 'no-matter-id',
216+
name: 'no-matter-name',
217+
email: 'no-matter-email',
218+
roles: ['time-tracker-admin', 'time-tracker-tester'],
219+
};
220+
221+
availableRoles.forEach((role) => {
222+
const isToggleEnabled = false;
223+
component.updateRole(role, user, isToggleEnabled);
224+
expect(store.dispatch).toHaveBeenCalledWith(new RevokeUserRole(user.id, role.name));
225+
});
226+
});
227+
228+
it('When we call ROLES variable should return available roles', () => {
229+
expect(component.ROLES).toEqual(ROLES);
230+
});
231+
182232
afterEach(() => {
183233
component.dtTrigger.unsubscribe();
184234
component.loadUsersSubscription.unsubscribe();

src/app/modules/users/components/users-list/users-list.component.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1+
import { GrantUserRole, RevokeUserRole } from './../../store/user.actions';
12
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
23
import { ActionsSubject, select, Store, Action } from '@ngrx/store';
34
import { DataTableDirective } from 'angular-datatables';
45
import { Observable, Subject, Subscription } from 'rxjs';
56
import { delay, filter } from 'rxjs/operators';
7+
import { ROLES } from 'src/environments/environment';
68
import { User } from '../../models/users';
7-
import {
8-
LoadUsers,
9-
UserActionTypes,
10-
AddUserToGroup,
11-
RemoveUserFromGroup,
12-
} from '../../store/user.actions';
9+
import { LoadUsers, UserActionTypes, AddUserToGroup, RemoveUserFromGroup } from '../../store/user.actions';
1310
import { getIsLoading } from '../../store/user.selectors';
1411

1512
@Component({
@@ -28,10 +25,11 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
2825
dtOptions: any = {};
2926
switchGroupsSubscription: Subscription;
3027

31-
constructor(
32-
private store: Store<User>,
33-
private actionsSubject$: ActionsSubject,
34-
) {
28+
public get ROLES() {
29+
return ROLES;
30+
}
31+
32+
constructor(private store: Store<User>, private actionsSubject$: ActionsSubject) {
3533
this.isLoading$ = store.pipe(delay(0), select(getIsLoading));
3634
}
3735

@@ -77,6 +75,11 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
7775
);
7876
}
7977

78+
updateRole(role: { name: string; value: string }, user: User, isToggleEnabled: boolean) {
79+
const action = isToggleEnabled ? new GrantUserRole(user.id, role.name) : new RevokeUserRole(user.id, role.name);
80+
this.store.dispatch(action);
81+
}
82+
8083
filterUserGroup(): Observable<Action> {
8184
return this.actionsSubject$.pipe(
8285
filter(

src/app/modules/users/store/user.actions.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,46 @@ describe('UserActions', () => {
5858

5959
expect(action.type).toEqual(actions.UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL);
6060
});
61+
62+
it('GrantUserRole type is UserActionTypes.GRANT_USER_ROLE', () => {
63+
const userId = 'no-matter-id';
64+
const roleId = 'no-matter-role-id';
65+
const action = new actions.GrantUserRole(userId, roleId);
66+
67+
expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE);
68+
});
69+
70+
it('GrantUserRoleSuccess type is UserActionTypes.GRANT_USER_ROLE_SUCCESS', () => {
71+
const user: User = { id: 'id', email: 'email', name: 'name' };
72+
const action = new actions.GrantUserRoleSuccess(user);
73+
74+
expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE_SUCCESS);
75+
});
76+
77+
it('GrantUserRoleFail type is UserActionTypes.GRANT_USER_ROLE_FAIL', () => {
78+
const action = new actions.GrantUserRoleFail('error');
79+
80+
expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE_FAIL);
81+
});
82+
83+
it('RevokeUserRole type is UserActionTypes.REVOKE_USER_ROLE', () => {
84+
const userId = 'no-matter-id';
85+
const roleId = 'no-matter-role-id';
86+
const action = new actions.RevokeUserRole(userId, roleId);
87+
88+
expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE);
89+
});
90+
91+
it('RevokeUserRoleSuccess type is UserActionTypes.REVOKE_USER_ROLE_SUCCESS', () => {
92+
const user: User = { id: 'id', email: 'email', name: 'name' };
93+
const action = new actions.RevokeUserRoleSuccess(user);
94+
95+
expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE_SUCCESS);
96+
});
97+
98+
it('RevokeUserRoleFail type is UserActionTypes.REVOKE_USER_ROLE_FAIL', () => {
99+
const action = new actions.RevokeUserRoleFail('error');
100+
101+
expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE_FAIL);
102+
});
61103
});

src/app/modules/users/store/user.actions.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ export enum UserActionTypes {
1111
REMOVE_USER_FROM_GROUP = '[User] REMOVE_USER_FROM_GROUP',
1212
REMOVE_USER_FROM_GROUP_SUCCESS = '[User] REMOVE_USER_FROM_GROUP_SUCCESS',
1313
REMOVE_USER_FROM_GROUP_FAIL = '[User] REMOVE_USER_FROM_GROUP_FAIL',
14-
DEFAULT_USER = '[USER] DEFAULT_USER',
14+
GRANT_USER_ROLE = '[User] GRANT_USER_ROLE',
15+
GRANT_USER_ROLE_SUCCESS = '[User] GRANT_USER_ROLE_SUCCESS',
16+
GRANT_USER_ROLE_FAIL = '[User] GRANT_USER_ROLE_FAIL',
17+
REVOKE_USER_ROLE = '[User] REVOKE_USER_ROLE',
18+
REVOKE_USER_ROLE_SUCCESS = '[User] REVOKE_USER_ROLE_SUCCESS',
19+
REVOKE_USER_ROLE_FAIL = '[User] REVOKE_USER_ROLE_FAIL',
20+
DEFAULT_USER = '[User] DEFAULT_USER',
1521
}
1622

1723
export class LoadUsers implements Action {
@@ -20,42 +26,72 @@ export class LoadUsers implements Action {
2026

2127
export class LoadUsersSuccess implements Action {
2228
readonly type = UserActionTypes.LOAD_USERS_SUCCESS;
23-
constructor(readonly payload: User[]) { }
29+
constructor(readonly payload: User[]) {}
2430
}
2531

2632
export class LoadUsersFail implements Action {
2733
public readonly type = UserActionTypes.LOAD_USERS_FAIL;
28-
constructor(public error: string) { }
34+
constructor(public error: string) {}
2935
}
3036

3137
export class AddUserToGroup implements Action {
3238
public readonly type = UserActionTypes.ADD_USER_TO_GROUP;
33-
constructor(public userId: string, public groupName: string) { }
39+
constructor(public userId: string, public groupName: string) {}
3440
}
3541

3642
export class AddUserToGroupSuccess implements Action {
3743
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_SUCCESS;
38-
constructor(readonly payload: User) { }
44+
constructor(readonly payload: User) {}
3945
}
4046

4147
export class AddUserToGroupFail implements Action {
4248
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_FAIL;
43-
constructor(public error: string) { }
49+
constructor(public error: string) {}
4450
}
4551

4652
export class RemoveUserFromGroup implements Action {
4753
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP;
48-
constructor(public userId: string, public groupName: string) { }
54+
constructor(public userId: string, public groupName: string) {}
4955
}
5056

5157
export class RemoveUserFromGroupSuccess implements Action {
5258
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS;
53-
constructor(readonly payload: User) { }
59+
constructor(readonly payload: User) {}
5460
}
5561

5662
export class RemoveUserFromGroupFail implements Action {
5763
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL;
58-
constructor(public error: string) { }
64+
constructor(public error: string) {}
65+
}
66+
67+
export class GrantUserRole implements Action {
68+
public readonly type = UserActionTypes.GRANT_USER_ROLE;
69+
constructor(public userId: string, public roleId: string) {}
70+
}
71+
72+
export class GrantUserRoleSuccess implements Action {
73+
public readonly type = UserActionTypes.GRANT_USER_ROLE_SUCCESS;
74+
constructor(readonly payload: User) {}
75+
}
76+
77+
export class GrantUserRoleFail implements Action {
78+
public readonly type = UserActionTypes.GRANT_USER_ROLE_FAIL;
79+
constructor(public error: string) {}
80+
}
81+
82+
export class RevokeUserRole implements Action {
83+
public readonly type = UserActionTypes.REVOKE_USER_ROLE;
84+
constructor(public userId: string, public roleId: string) {}
85+
}
86+
87+
export class RevokeUserRoleSuccess implements Action {
88+
public readonly type = UserActionTypes.REVOKE_USER_ROLE_SUCCESS;
89+
constructor(readonly payload: User) {}
90+
}
91+
92+
export class RevokeUserRoleFail implements Action {
93+
public readonly type = UserActionTypes.REVOKE_USER_ROLE_FAIL;
94+
constructor(public error: string) {}
5995
}
6096

6197
export class DefaultUser implements Action {
@@ -72,4 +108,10 @@ export type UserActions =
72108
| AddUserToGroupFail
73109
| RemoveUserFromGroup
74110
| RemoveUserFromGroupSuccess
75-
| RemoveUserFromGroupFail;
111+
| RemoveUserFromGroupFail
112+
| GrantUserRole
113+
| GrantUserRoleSuccess
114+
| GrantUserRoleFail
115+
| RevokeUserRole
116+
| RevokeUserRoleSuccess
117+
| RevokeUserRoleFail;

0 commit comments

Comments
 (0)