Skip to content

Commit 84fe2c0

Browse files
committed
fix: TT-310 Reports does not work with groups fixed
1 parent 87b2053 commit 84fe2c0

File tree

10 files changed

+442
-55
lines changed

10 files changed

+442
-55
lines changed
Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
<div class="table-responsive">
2-
<table
3-
*ngIf="users"
4-
class="table table-sm table-bordered table-striped mb-0"
5-
datatable
6-
[dtTrigger]="dtTrigger"
7-
[dtOptions]="dtOptions"
8-
>
2+
<table *ngIf="users" class="table table-sm table-bordered table-striped mb-0" datatable [dtTrigger]="dtTrigger"
3+
[dtOptions]="dtOptions">
94
<thead class="thead-blue">
105
<tr class="d-flex flex-wrap">
116
<th class="col-4">User Email</th>
@@ -19,20 +14,14 @@
1914
<td class="col-4 text-break">{{ user.email }}</td>
2015
<td class="col-5 text-break">{{ user.name }}</td>
2116
<td class="col-3 text-center">
22-
<ui-switch
23-
size="small"
24-
(change)="switchGroup('time-tracker-admin', user)"
25-
[checked]="user.groups.includes('time-tracker-admin')"
26-
></ui-switch>
17+
<ui-switch size="small" (change)="switchGroup('time-tracker-admin', user); updateRole(ROLES.admin, user);"
18+
[checked]="user.groups.includes('time-tracker-admin')"></ui-switch>
2719
admin
28-
<ui-switch
29-
size="small"
30-
(change)="switchGroup('time-tracker-tester', user)"
31-
[checked]="user.groups.includes('time-tracker-tester')"
32-
></ui-switch>
20+
<ui-switch size="small" (change)="switchGroup('time-tracker-tester', user)"
21+
[checked]="user.groups.includes('time-tracker-tester')"></ui-switch>
3322
test
3423
</td>
3524
</tr>
3625
</tbody>
3726
</table>
38-
</div>
27+
</div>

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

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ 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';
149

1510
describe('UsersListComponent', () => {
1611
let component: UsersListComponent;
@@ -39,10 +34,7 @@ describe('UsersListComponent', () => {
3934
TestBed.configureTestingModule({
4035
imports: [NgxPaginationModule, DataTablesModule],
4136
declarations: [UsersListComponent],
42-
providers: [
43-
provideMockStore({ initialState: state }),
44-
{ provide: ActionsSubject, useValue: actionSub },
45-
],
37+
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
4638
}).compileComponents();
4739
})
4840
);
@@ -100,10 +92,7 @@ describe('UsersListComponent', () => {
10092
});
10193
});
10294

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

10897
AddGroupTypes.map((param) => {
10998
it(`When user switchGroup to ${param.groupName} and doesn't belong to any group, should add ${param.groupName} group to user`, () => {
@@ -179,6 +168,60 @@ describe('UsersListComponent', () => {
179168
expect(component.dtElement.dtInstance.then).toHaveBeenCalled();
180169
});
181170

171+
it('When the user does not have the role, this role must be added', () => {
172+
const availableRoles = [
173+
{
174+
name: 'admin',
175+
value: 'time-tracker-admin',
176+
},
177+
{
178+
name: 'test',
179+
value: 'time-tracker-tester',
180+
},
181+
];
182+
183+
spyOn(store, 'dispatch');
184+
185+
const user = {
186+
id: 'no-matter-id',
187+
name: 'no-matter-name',
188+
email: 'no-matter-email',
189+
roles: [],
190+
};
191+
192+
availableRoles.forEach((role) => {
193+
component.updateRole(role, user);
194+
expect(store.dispatch).toHaveBeenCalledWith(new GrantUserRole(user.id, role.name));
195+
});
196+
});
197+
198+
it('When the user has the role, this role must be removed', () => {
199+
const availableRoles = [
200+
{
201+
name: 'admin',
202+
value: 'time-tracker-admin',
203+
},
204+
{
205+
name: 'test',
206+
value: 'time-tracker-tester',
207+
},
208+
];
209+
210+
spyOn(store, 'dispatch');
211+
212+
const user = {
213+
id: 'no-matter-id',
214+
name: 'no-matter-name',
215+
email: 'no-matter-email',
216+
roles: ['time-tracker-admin', 'time-tracker-tester'],
217+
};
218+
219+
availableRoles.forEach((role) => {
220+
component.updateRole(role, user);
221+
expect(store.dispatch).toHaveBeenCalledWith(new RevokeUserRole(user.id, role.name));
222+
});
223+
});
224+
182225
afterEach(() => {
183226
component.dtTrigger.unsubscribe();
184227
component.loadUsersSubscription.unsubscribe();

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
import { GrantUserRole } 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';
11+
import { RevokeUserRole } from '../../store/user.actions';
1412

1513
@Component({
1614
selector: 'app-users-list',
@@ -28,10 +26,11 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
2826
dtOptions: any = {};
2927
switchGroupsSubscription: Subscription;
3028

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

@@ -77,6 +76,13 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
7776
);
7877
}
7978

79+
updateRole(role: { name: string; value: string }, user: User) {
80+
const userHasRole = user.roles.includes(role.value);
81+
const action = userHasRole ? new RevokeUserRole(user.id, role.name) : new GrantUserRole(user.id, role.name);
82+
83+
this.store.dispatch(action);
84+
}
85+
8086
filterUserGroup(): Observable<Action> {
8187
return this.actionsSubject$.pipe(
8288
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)