Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: TT-190 Use add/remove groups endpoints in users section in UI
  • Loading branch information
LEON12699 committed Mar 30, 2021
commit 14711ce988d43ab91f3aa8506b7f648e7e714ed2
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<tr class="d-flex flex-wrap">
<th class="col-4">User Email</th>
<th class="col-5">Names</th>
<th class="col-3">Roles</th>
<th class="col-3">{{ isUserGroupsToggleOn ? 'Groups' : 'Roles' }}</th>
</tr>
</thead>
<app-loading-bar *ngIf="isLoading$ | async"></app-loading-bar>
Expand All @@ -19,20 +19,35 @@
<td class="col-4 text-break">{{ user.email }}</td>
<td class="col-5 text-break">{{ user.name }}</td>
<td class="col-3 text-center">
<div>
<div *ngIf="isUserGroupsToggleOn">
<ui-switch
size="small"
(change)="switchGroups(user.id, user.groups, 'admin', 'time-tracker-admin')"
(change)="switchGroup(user.id, user.groups, 'time-tracker-admin')"
[checked]="user.groups.includes('time-tracker-admin')"
></ui-switch>
admin
<ui-switch
size="small"
(change)="switchGroups(user.id, user.groups, 'test', 'time-tracker-tester')"
(change)="switchGroup(user.id, user.groups, 'time-tracker-tester')"
[checked]="user.groups.includes('time-tracker-tester')"
></ui-switch>
test
</div>

<div *ngIf="!isUserGroupsToggleOn">
<ui-switch
size="small"
(change)="switchRole(user.id, user.roles, 'admin', 'time-tracker-admin')"
[checked]="user.roles.includes('time-tracker-admin')"
></ui-switch>
admin
<ui-switch
size="small"
(change)="switchRole(user.id, user.roles, 'test', 'time-tracker-tester')"
[checked]="user.roles.includes('time-tracker-tester')"
></ui-switch>
test
</div>
</td>
</tr>
</tbody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { MockStore, provideMockStore } from '@ngrx/store/testing';

import { NgxPaginationModule } from 'ngx-pagination';
import { UsersListComponent } from './users-list.component';
import { UserActionTypes, UserState, LoadUsers, GrantRoleUser, RevokeRoleUser } from '../../store';
import {
UserActionTypes,
UserState,
LoadUsers,
GrantRoleUser,
RevokeRoleUser,
AddUserToGroup,
RemoveUserFromGroup,
} from '../../store';
import { ActionsSubject } from '@ngrx/store';
import { DataTablesModule } from 'angular-datatables';
import { Observable, of } from 'rxjs';
import { FeatureToggleProvider } from 'src/app/modules/shared/feature-toggles/feature-toggle-provider.service';
import { AppConfigurationClient } from '@azure/app-configuration';
import { FeatureFilterProvider } from '../../../shared/feature-toggles/filters/feature-filter-provider.service';
import { AzureAdB2CService } from '../../../login/services/azure.ad.b2c.service';

describe('UsersListComponent', () => {
let component: UsersListComponent;
let fixture: ComponentFixture<UsersListComponent>;
let store: MockStore<UserState>;
const actionSub: ActionsSubject = new ActionsSubject();
const fakeAppConfigurationConnectionString = 'Endpoint=http://fake.foo;Id=fake.id;Secret=fake.secret';
let service: FeatureManagerService;
let fakeFeatureToggleProvider;

const state: UserState = {
data: [
{
name: 'name',
email: 'email',
roles: ['admin', 'test'],
groups: ['time-tracker-admin', 'time-tracker-tester'],
id: 'id',
tenant_id: 'tenant id',
deleted: 'delete',
Expand All @@ -30,10 +47,20 @@ describe('UsersListComponent', () => {

beforeEach(
waitForAsync(() => {
fakeFeatureToggleProvider = new FeatureToggleProvider(
new AppConfigurationClient(fakeAppConfigurationConnectionString),
new FeatureFilterProvider(new AzureAdB2CService())
);
service = new FeatureManagerService(fakeFeatureToggleProvider);

TestBed.configureTestingModule({
imports: [NgxPaginationModule, DataTablesModule],
declarations: [UsersListComponent],
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
providers: [
provideMockStore({ initialState: state }),
{ provide: ActionsSubject, useValue: actionSub },
{ provide: FeatureManagerService, useValue: service }
],
}).compileComponents();
})
);
Expand Down Expand Up @@ -91,6 +118,27 @@ describe('UsersListComponent', () => {
});
});

const actionsGroupsParams = [
{ actionType: UserActionTypes.ADD_USER_TO_GROUP_SUCCESS },
{ actionType: UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS },
];

actionsGroupsParams.map((param) => {
it(`When action ${param.actionType} is dispatched should triggered load Users action`, () => {
spyOn(store, 'dispatch');

const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
const action = {
type: param.actionType,
payload: state.data,
};

actionSubject.next(action);

expect(store.dispatch).toHaveBeenCalledWith(new LoadUsers());
});
});

const grantRoleTypes = [
{ roleId: 'admin', roleValue: 'time-tracker-admin' },
{ roleId: 'test', roleValue: 'time-tracker-tester' },
Expand All @@ -111,6 +159,25 @@ describe('UsersListComponent', () => {
});
});

const AddGroupTypes = [
{ groupName: 'time-tracker-admin' },
{ groupName: 'time-tracker-tester' }
];

AddGroupTypes.map((param) => {
it(`When user switchGroup to ${param.groupName} and doesn't belong to any group, should add ${param.groupName} group to user`, () => {
const groupName = param.groupName;
const userGroups = [];
const userId = 'userId';

spyOn(store, 'dispatch');

component.switchGroup(userId, userGroups, groupName);

expect(store.dispatch).toHaveBeenCalledWith(new AddUserToGroup(userId, groupName));
});
});

const revokeRoleTypes = [
{ roleId: 'admin', roleValue: 'time-tracker-admin', userRoles: ['time-tracker-admin'] },
{ roleId: 'test', roleValue: 'time-tracker-tester', userRoles: ['time-tracker-tester'] },
Expand All @@ -131,6 +198,25 @@ describe('UsersListComponent', () => {
});
});

const removeGroupTypes = [
{ groupName: 'time-tracker-admin', userGroups: ['time-tracker-admin'] },
{ groupName: 'time-tracker-tester', userGroups: ['time-tracker-tester'] },
];

removeGroupTypes.map((param) => {
it(`When user switchGroup to ${param.groupName} and belongs to group, should remove ${param.groupName} group from user`, () => {
const groupName = param.groupName;
const userGroups = param.userGroups;
const userId = 'userId';

spyOn(store, 'dispatch');

component.switchGroup(userId, userGroups, groupName);

expect(store.dispatch).toHaveBeenCalledWith(new RemoveUserFromGroup(userId, groupName));
});
});

it('on success load users, the data of roles should be an array', () => {
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
const action = {
Expand All @@ -145,6 +231,20 @@ describe('UsersListComponent', () => {
});
});

it('on success load users, the data of groups should be an array', () => {
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
const action = {
type: UserActionTypes.LOAD_USERS_SUCCESS,
payload: state.data,
};

actionSubject.next(action);

component.users.map((user) => {
expect(user.groups).toEqual(['time-tracker-admin', 'time-tracker-tester']);
});
});

it('on success load users, the datatable should be reloaded', async () => {
const actionSubject = TestBed.inject(ActionsSubject);
const action = {
Expand All @@ -158,6 +258,27 @@ describe('UsersListComponent', () => {
expect(component.dtElement.dtInstance.then).toHaveBeenCalled();
});

it('When Component is created, should call the feature toggle method', () => {
spyOn(component, 'isFeatureToggleActivated').and.returnValue(of(true));

component.ngOnInit();

expect(component.isFeatureToggleActivated).toHaveBeenCalled();
expect(component.isUserGroupsToggleOn).toBe(true);
});

const toggleValues = [true, false];
toggleValues.map((toggleValue) => {
it(`when FeatureToggle is ${toggleValue} should return ${toggleValue}`, () => {
spyOn(service, 'isToggleEnabledForUser').and.returnValue(of(toggleValue));

const isFeatureToggleActivated: Observable<boolean> = component.isFeatureToggleActivated();

expect(service.isToggleEnabledForUser).toHaveBeenCalled();
isFeatureToggleActivated.subscribe((value) => expect(value).toEqual(toggleValue));
});
});

afterEach(() => {
component.dtTrigger.unsubscribe();
component.loadUsersSubscription.unsubscribe();
Expand Down
34 changes: 19 additions & 15 deletions src/app/modules/users/components/users-list/users-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActionsSubject, select, Store } from '@ngrx/store';
import { DataTableDirective } from 'angular-datatables';
import { Observable, Subject, Subscription} from 'rxjs';
import { Observable, Subject, Subscription } from 'rxjs';
import { delay, filter, map } from 'rxjs/operators';
import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service';
import { User } from '../../models/users';
import { GrantRoleUser, LoadUsers, RevokeRoleUser, UserActionTypes, AddUserToGroup, RemoveUserToGroup} from '../../store/user.actions';
import {
GrantRoleUser,
LoadUsers,
RevokeRoleUser,
UserActionTypes,
AddUserToGroup,
RemoveUserFromGroup,
} from '../../store/user.actions';
import { getIsLoading } from '../../store/user.selectors';

@Component({
Expand All @@ -24,8 +31,7 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
dtOptions: any = {};
switchGroupsSubscription: Subscription;
isEnableToggleSubscription: Subscription;
isUserRoleToggleOn;
flakyToggle: true;
isUserGroupsToggleOn: boolean;

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

this.isEnableToggleSubscription = this.isFeatureToggleActivated().subscribe((flag) => {
this.isUserRoleToggleOn = flag;
console.log('in subscription', this.isUserRoleToggleOn);
this.isUserGroupsToggleOn = flag;
});

this.switchGroupsSubscription = this.actionsSubject$
.pipe(
filter(
(action: any) =>
action.type === UserActionTypes.ADD_USER_TO_GROUP_SUCCESS ||
action.type === UserActionTypes.REMOVE_USER_TO_GROUP_SUCCESS
action.type === UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS
)
)
.subscribe((action) => {
this.store.dispatch(new LoadUsers());
this.rerenderDataTable();
});

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

switchGroup(userId: string, userGroups: string[], groupName: string) {
userGroups.includes(groupName)
? this.store.dispatch(new RemoveUserFromGroup(userId, groupName))
: this.store.dispatch(new AddUserToGroup(userId, groupName));
}

isFeatureToggleActivated() {
return this.featureManagerService.isToggleEnabledForUser('ui-list-technologies').pipe(
return this.featureManagerService.isToggleEnabledForUser('switch-group').pipe(
map((enabled) => {
return enabled === true ? true : false;
})
);
}

switchGroups(userId: string, userGroups: string[], groupname: string, groupValue: string) {
userGroups.includes(groupValue)
? this.store.dispatch(new RemoveUserToGroup(userId, groupname))
: this.store.dispatch(new AddUserToGroup(userId, groupname));
}
}
36 changes: 0 additions & 36 deletions src/app/modules/users/services/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,58 +52,22 @@ describe('UsersService', () => {
expect(grantRoleRequest.request.method).toBe('POST');
});

<<<<<<< HEAD
<<<<<<< HEAD
it('add user to group', () => {
=======
it('add group to a User', () => {
>>>>>>> feat: TT-188 add & remove groups to user service
=======
it('add user to group', () => {
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
const userId = 'userId';
const group = 'admin';
const addGroupURL = `${service.baseUrl}/${userId}/groups/add`;

<<<<<<< HEAD
<<<<<<< HEAD
service.addUserToGroup(userId, group).subscribe();
=======
service.addGroupToUser(userId, group).subscribe();
>>>>>>> feat: TT-188 add & remove groups to user service
=======
service.addUserToGroup(userId, group).subscribe();
>>>>>>> refactor: TT-188 refactor some names

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

<<<<<<< HEAD
<<<<<<< HEAD
it('remove user from group', () => {
=======
it('remove group to a User', () => {
>>>>>>> feat: TT-188 add & remove groups to user service
=======
it('remove user from group', () => {
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
const userId = 'userId';
const group = 'admin';
const removeGroupURL = `${service.baseUrl}/${userId}/groups/remove`;

<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
service.removeUserFromGroup(userId, group).subscribe();
=======
service.removeGroupToUser(userId, group).subscribe();
>>>>>>> feat: TT-188 add & remove groups to user service
=======
service.removeUserToGroup(userId, group).subscribe();
>>>>>>> refactor: TT-188 refactor some names
=======
service.removeUserFromGroup(userId, group).subscribe();
>>>>>>> refactor: TT-188 refactor 'removeTo' to 'removeFrom' references

expect(httpMock.expectOne(removeGroupURL).request.method).toBe('POST');
});
Expand Down
Loading