Skip to content

Commit 14711ce

Browse files
committed
feat: TT-190 Use add/remove groups endpoints in users section in UI
1 parent 321d812 commit 14711ce

File tree

11 files changed

+162
-614
lines changed

11 files changed

+162
-614
lines changed

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<tr class="d-flex flex-wrap">
1111
<th class="col-4">User Email</th>
1212
<th class="col-5">Names</th>
13-
<th class="col-3">Roles</th>
13+
<th class="col-3">{{ isUserGroupsToggleOn ? 'Groups' : 'Roles' }}</th>
1414
</tr>
1515
</thead>
1616
<app-loading-bar *ngIf="isLoading$ | async"></app-loading-bar>
@@ -19,20 +19,35 @@
1919
<td class="col-4 text-break">{{ user.email }}</td>
2020
<td class="col-5 text-break">{{ user.name }}</td>
2121
<td class="col-3 text-center">
22-
<div>
22+
<div *ngIf="isUserGroupsToggleOn">
2323
<ui-switch
2424
size="small"
25-
(change)="switchGroups(user.id, user.groups, 'admin', 'time-tracker-admin')"
25+
(change)="switchGroup(user.id, user.groups, 'time-tracker-admin')"
2626
[checked]="user.groups.includes('time-tracker-admin')"
2727
></ui-switch>
2828
admin
2929
<ui-switch
3030
size="small"
31-
(change)="switchGroups(user.id, user.groups, 'test', 'time-tracker-tester')"
31+
(change)="switchGroup(user.id, user.groups, 'time-tracker-tester')"
3232
[checked]="user.groups.includes('time-tracker-tester')"
3333
></ui-switch>
3434
test
3535
</div>
36+
37+
<div *ngIf="!isUserGroupsToggleOn">
38+
<ui-switch
39+
size="small"
40+
(change)="switchRole(user.id, user.roles, 'admin', 'time-tracker-admin')"
41+
[checked]="user.roles.includes('time-tracker-admin')"
42+
></ui-switch>
43+
admin
44+
<ui-switch
45+
size="small"
46+
(change)="switchRole(user.id, user.roles, 'test', 'time-tracker-tester')"
47+
[checked]="user.roles.includes('time-tracker-tester')"
48+
></ui-switch>
49+
test
50+
</div>
3651
</td>
3752
</tr>
3853
</tbody>

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

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
1+
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';
12
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
23
import { MockStore, provideMockStore } from '@ngrx/store/testing';
3-
44
import { NgxPaginationModule } from 'ngx-pagination';
55
import { UsersListComponent } from './users-list.component';
6-
import { UserActionTypes, UserState, LoadUsers, GrantRoleUser, RevokeRoleUser } from '../../store';
6+
import {
7+
UserActionTypes,
8+
UserState,
9+
LoadUsers,
10+
GrantRoleUser,
11+
RevokeRoleUser,
12+
AddUserToGroup,
13+
RemoveUserFromGroup,
14+
} from '../../store';
715
import { ActionsSubject } from '@ngrx/store';
816
import { DataTablesModule } from 'angular-datatables';
17+
import { Observable, of } from 'rxjs';
18+
import { FeatureToggleProvider } from 'src/app/modules/shared/feature-toggles/feature-toggle-provider.service';
19+
import { AppConfigurationClient } from '@azure/app-configuration';
20+
import { FeatureFilterProvider } from '../../../shared/feature-toggles/filters/feature-filter-provider.service';
21+
import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service';
922

1023
describe('UsersListComponent', () => {
1124
let component: UsersListComponent;
1225
let fixture: ComponentFixture<UsersListComponent>;
1326
let store: MockStore<UserState>;
1427
const actionSub: ActionsSubject = new ActionsSubject();
28+
const fakeAppConfigurationConnectionString = 'Endpoint=http://fake.foo;Id=fake.id;Secret=fake.secret';
29+
let service: FeatureManagerService;
30+
let fakeFeatureToggleProvider;
1531

1632
const state: UserState = {
1733
data: [
1834
{
1935
name: 'name',
2036
email: 'email',
2137
roles: ['admin', 'test'],
38+
groups: ['time-tracker-admin', 'time-tracker-tester'],
2239
id: 'id',
2340
tenant_id: 'tenant id',
2441
deleted: 'delete',
@@ -30,10 +47,20 @@ describe('UsersListComponent', () => {
3047

3148
beforeEach(
3249
waitForAsync(() => {
50+
fakeFeatureToggleProvider = new FeatureToggleProvider(
51+
new AppConfigurationClient(fakeAppConfigurationConnectionString),
52+
new FeatureFilterProvider(new AzureAdB2CService())
53+
);
54+
service = new FeatureManagerService(fakeFeatureToggleProvider);
55+
3356
TestBed.configureTestingModule({
3457
imports: [NgxPaginationModule, DataTablesModule],
3558
declarations: [UsersListComponent],
36-
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
59+
providers: [
60+
provideMockStore({ initialState: state }),
61+
{ provide: ActionsSubject, useValue: actionSub },
62+
{ provide: FeatureManagerService, useValue: service }
63+
],
3764
}).compileComponents();
3865
})
3966
);
@@ -91,6 +118,27 @@ describe('UsersListComponent', () => {
91118
});
92119
});
93120

121+
const actionsGroupsParams = [
122+
{ actionType: UserActionTypes.ADD_USER_TO_GROUP_SUCCESS },
123+
{ actionType: UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS },
124+
];
125+
126+
actionsGroupsParams.map((param) => {
127+
it(`When action ${param.actionType} is dispatched should triggered load Users action`, () => {
128+
spyOn(store, 'dispatch');
129+
130+
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
131+
const action = {
132+
type: param.actionType,
133+
payload: state.data,
134+
};
135+
136+
actionSubject.next(action);
137+
138+
expect(store.dispatch).toHaveBeenCalledWith(new LoadUsers());
139+
});
140+
});
141+
94142
const grantRoleTypes = [
95143
{ roleId: 'admin', roleValue: 'time-tracker-admin' },
96144
{ roleId: 'test', roleValue: 'time-tracker-tester' },
@@ -111,6 +159,25 @@ describe('UsersListComponent', () => {
111159
});
112160
});
113161

162+
const AddGroupTypes = [
163+
{ groupName: 'time-tracker-admin' },
164+
{ groupName: 'time-tracker-tester' }
165+
];
166+
167+
AddGroupTypes.map((param) => {
168+
it(`When user switchGroup to ${param.groupName} and doesn't belong to any group, should add ${param.groupName} group to user`, () => {
169+
const groupName = param.groupName;
170+
const userGroups = [];
171+
const userId = 'userId';
172+
173+
spyOn(store, 'dispatch');
174+
175+
component.switchGroup(userId, userGroups, groupName);
176+
177+
expect(store.dispatch).toHaveBeenCalledWith(new AddUserToGroup(userId, groupName));
178+
});
179+
});
180+
114181
const revokeRoleTypes = [
115182
{ roleId: 'admin', roleValue: 'time-tracker-admin', userRoles: ['time-tracker-admin'] },
116183
{ roleId: 'test', roleValue: 'time-tracker-tester', userRoles: ['time-tracker-tester'] },
@@ -131,6 +198,25 @@ describe('UsersListComponent', () => {
131198
});
132199
});
133200

201+
const removeGroupTypes = [
202+
{ groupName: 'time-tracker-admin', userGroups: ['time-tracker-admin'] },
203+
{ groupName: 'time-tracker-tester', userGroups: ['time-tracker-tester'] },
204+
];
205+
206+
removeGroupTypes.map((param) => {
207+
it(`When user switchGroup to ${param.groupName} and belongs to group, should remove ${param.groupName} group from user`, () => {
208+
const groupName = param.groupName;
209+
const userGroups = param.userGroups;
210+
const userId = 'userId';
211+
212+
spyOn(store, 'dispatch');
213+
214+
component.switchGroup(userId, userGroups, groupName);
215+
216+
expect(store.dispatch).toHaveBeenCalledWith(new RemoveUserFromGroup(userId, groupName));
217+
});
218+
});
219+
134220
it('on success load users, the data of roles should be an array', () => {
135221
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
136222
const action = {
@@ -145,6 +231,20 @@ describe('UsersListComponent', () => {
145231
});
146232
});
147233

234+
it('on success load users, the data of groups should be an array', () => {
235+
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
236+
const action = {
237+
type: UserActionTypes.LOAD_USERS_SUCCESS,
238+
payload: state.data,
239+
};
240+
241+
actionSubject.next(action);
242+
243+
component.users.map((user) => {
244+
expect(user.groups).toEqual(['time-tracker-admin', 'time-tracker-tester']);
245+
});
246+
});
247+
148248
it('on success load users, the datatable should be reloaded', async () => {
149249
const actionSubject = TestBed.inject(ActionsSubject);
150250
const action = {
@@ -158,6 +258,27 @@ describe('UsersListComponent', () => {
158258
expect(component.dtElement.dtInstance.then).toHaveBeenCalled();
159259
});
160260

261+
it('When Component is created, should call the feature toggle method', () => {
262+
spyOn(component, 'isFeatureToggleActivated').and.returnValue(of(true));
263+
264+
component.ngOnInit();
265+
266+
expect(component.isFeatureToggleActivated).toHaveBeenCalled();
267+
expect(component.isUserGroupsToggleOn).toBe(true);
268+
});
269+
270+
const toggleValues = [true, false];
271+
toggleValues.map((toggleValue) => {
272+
it(`when FeatureToggle is ${toggleValue} should return ${toggleValue}`, () => {
273+
spyOn(service, 'isToggleEnabledForUser').and.returnValue(of(toggleValue));
274+
275+
const isFeatureToggleActivated: Observable<boolean> = component.isFeatureToggleActivated();
276+
277+
expect(service.isToggleEnabledForUser).toHaveBeenCalled();
278+
isFeatureToggleActivated.subscribe((value) => expect(value).toEqual(toggleValue));
279+
});
280+
});
281+
161282
afterEach(() => {
162283
component.dtTrigger.unsubscribe();
163284
component.loadUsersSubscription.unsubscribe();

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
22
import { ActionsSubject, select, Store } from '@ngrx/store';
33
import { DataTableDirective } from 'angular-datatables';
4-
import { Observable, Subject, Subscription} from 'rxjs';
4+
import { Observable, Subject, Subscription } from 'rxjs';
55
import { delay, filter, map } from 'rxjs/operators';
66
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';
77
import { User } from '../../models/users';
8-
import { GrantRoleUser, LoadUsers, RevokeRoleUser, UserActionTypes, AddUserToGroup, RemoveUserToGroup} from '../../store/user.actions';
8+
import {
9+
GrantRoleUser,
10+
LoadUsers,
11+
RevokeRoleUser,
12+
UserActionTypes,
13+
AddUserToGroup,
14+
RemoveUserFromGroup,
15+
} from '../../store/user.actions';
916
import { getIsLoading } from '../../store/user.selectors';
1017

1118
@Component({
@@ -24,8 +31,7 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
2431
dtOptions: any = {};
2532
switchGroupsSubscription: Subscription;
2633
isEnableToggleSubscription: Subscription;
27-
isUserRoleToggleOn;
28-
flakyToggle: true;
34+
isUserGroupsToggleOn: boolean;
2935

3036
constructor(
3137
private store: Store<User>,
@@ -45,21 +51,19 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
4551
});
4652

4753
this.isEnableToggleSubscription = this.isFeatureToggleActivated().subscribe((flag) => {
48-
this.isUserRoleToggleOn = flag;
49-
console.log('in subscription', this.isUserRoleToggleOn);
54+
this.isUserGroupsToggleOn = flag;
5055
});
5156

5257
this.switchGroupsSubscription = this.actionsSubject$
5358
.pipe(
5459
filter(
5560
(action: any) =>
5661
action.type === UserActionTypes.ADD_USER_TO_GROUP_SUCCESS ||
57-
action.type === UserActionTypes.REMOVE_USER_TO_GROUP_SUCCESS
62+
action.type === UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS
5863
)
5964
)
6065
.subscribe((action) => {
6166
this.store.dispatch(new LoadUsers());
62-
this.rerenderDataTable();
6367
});
6468

6569
this.switchRoleSubscription = this.actionsSubject$
@@ -102,17 +106,17 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
102106
: this.store.dispatch(new GrantRoleUser(userId, roleId));
103107
}
104108

109+
switchGroup(userId: string, userGroups: string[], groupName: string) {
110+
userGroups.includes(groupName)
111+
? this.store.dispatch(new RemoveUserFromGroup(userId, groupName))
112+
: this.store.dispatch(new AddUserToGroup(userId, groupName));
113+
}
114+
105115
isFeatureToggleActivated() {
106-
return this.featureManagerService.isToggleEnabledForUser('ui-list-technologies').pipe(
116+
return this.featureManagerService.isToggleEnabledForUser('switch-group').pipe(
107117
map((enabled) => {
108118
return enabled === true ? true : false;
109119
})
110120
);
111121
}
112-
113-
switchGroups(userId: string, userGroups: string[], groupname: string, groupValue: string) {
114-
userGroups.includes(groupValue)
115-
? this.store.dispatch(new RemoveUserToGroup(userId, groupname))
116-
: this.store.dispatch(new AddUserToGroup(userId, groupname));
117-
}
118122
}

src/app/modules/users/services/users.service.spec.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,58 +52,22 @@ describe('UsersService', () => {
5252
expect(grantRoleRequest.request.method).toBe('POST');
5353
});
5454

55-
<<<<<<< HEAD
56-
<<<<<<< HEAD
5755
it('add user to group', () => {
58-
=======
59-
it('add group to a User', () => {
60-
>>>>>>> feat: TT-188 add & remove groups to user service
61-
=======
62-
it('add user to group', () => {
63-
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
6456
const userId = 'userId';
6557
const group = 'admin';
6658
const addGroupURL = `${service.baseUrl}/${userId}/groups/add`;
6759

68-
<<<<<<< HEAD
69-
<<<<<<< HEAD
70-
service.addUserToGroup(userId, group).subscribe();
71-
=======
72-
service.addGroupToUser(userId, group).subscribe();
73-
>>>>>>> feat: TT-188 add & remove groups to user service
74-
=======
7560
service.addUserToGroup(userId, group).subscribe();
76-
>>>>>>> refactor: TT-188 refactor some names
7761

7862
expect(httpMock.expectOne(addGroupURL).request.method).toBe('POST');
7963
});
8064

81-
<<<<<<< HEAD
82-
<<<<<<< HEAD
8365
it('remove user from group', () => {
84-
=======
85-
it('remove group to a User', () => {
86-
>>>>>>> feat: TT-188 add & remove groups to user service
87-
=======
88-
it('remove user from group', () => {
89-
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
9066
const userId = 'userId';
9167
const group = 'admin';
9268
const removeGroupURL = `${service.baseUrl}/${userId}/groups/remove`;
9369

94-
<<<<<<< HEAD
95-
<<<<<<< HEAD
96-
<<<<<<< HEAD
97-
service.removeUserFromGroup(userId, group).subscribe();
98-
=======
99-
service.removeGroupToUser(userId, group).subscribe();
100-
>>>>>>> feat: TT-188 add & remove groups to user service
101-
=======
102-
service.removeUserToGroup(userId, group).subscribe();
103-
>>>>>>> refactor: TT-188 refactor some names
104-
=======
10570
service.removeUserFromGroup(userId, group).subscribe();
106-
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
10771

10872
expect(httpMock.expectOne(removeGroupURL).request.method).toBe('POST');
10973
});

0 commit comments

Comments
 (0)