Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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,7 +19,22 @@
<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)="switchGroup('time-tracker-admin', user)"
[checked]="user.groups.includes('time-tracker-admin')"
></ui-switch>
admin
<ui-switch
size="small"
(change)="switchGroup('time-tracker-tester', user)"
[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')"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
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 { User } from '../../../user/models/user';
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 +48,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 +119,27 @@ describe('UsersListComponent', () => {
});
});

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

actionGroupParams.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 +160,32 @@ 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 user = {
name: 'name',
email: 'email',
roles: [],
groups: [],
id: 'id',
tenant_id: 'tenant id',
deleted: 'delete',
} ;

spyOn(store, 'dispatch');

component.switchGroup(groupName, user);

expect(store.dispatch).toHaveBeenCalledWith(new AddUserToGroup(user.id, 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 +206,33 @@ 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 user = {
name: 'name',
email: 'email',
roles: [],
groups: param.userGroups,
id: 'id',
tenant_id: 'tenant id',
deleted: 'delete',
} ;


spyOn(store, 'dispatch');

component.switchGroup(groupName, user);

expect(store.dispatch).toHaveBeenCalledWith(new RemoveUserFromGroup(user.id, 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 +247,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 +274,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
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActionsSubject, select, Store } from '@ngrx/store';
import { ActionsSubject, select, Store, Action } from '@ngrx/store';
import { DataTableDirective } from 'angular-datatables';
import { Observable, Subject, Subscription } from 'rxjs';
import { delay, filter } from 'rxjs/operators';
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 } from '../../store/user.actions';
import {
GrantRoleUser,
LoadUsers,
RevokeRoleUser,
UserActionTypes,
AddUserToGroup,
RemoveUserFromGroup,
} from '../../store/user.actions';
import { getIsLoading } from '../../store/user.selectors';

@Component({
Expand All @@ -21,8 +29,15 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild(DataTableDirective, { static: false })
dtElement: DataTableDirective;
dtOptions: any = {};
switchGroupsSubscription: Subscription;
isEnableToggleSubscription: Subscription;
isUserGroupsToggleOn: boolean;

constructor(private store: Store<User>, private actionsSubject$: ActionsSubject) {
constructor(
private store: Store<User>,
private actionsSubject$: ActionsSubject,
private featureManagerService: FeatureManagerService
) {
this.isLoading$ = store.pipe(delay(0), select(getIsLoading));
}

Expand All @@ -35,6 +50,14 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
this.rerenderDataTable();
});

this.isEnableToggleSubscription = this.isFeatureToggleActivated().subscribe((flag) => {
this.isUserGroupsToggleOn = flag;
});

this.switchGroupsSubscription = this.filterUserGroup().subscribe((action) => {
this.store.dispatch(new LoadUsers());
});

this.switchRoleSubscription = this.actionsSubject$
.pipe(
filter(
Expand All @@ -55,6 +78,7 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnDestroy() {
this.loadUsersSubscription.unsubscribe();
this.dtTrigger.unsubscribe();
this.isEnableToggleSubscription.unsubscribe();
}

private rerenderDataTable(): void {
Expand All @@ -73,4 +97,27 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
? this.store.dispatch(new RevokeRoleUser(userId, roleId))
: this.store.dispatch(new GrantRoleUser(userId, roleId));
}

switchGroup(groupName: string, user: User): void {
this.store.dispatch(
user.groups.includes(groupName)
? new RemoveUserFromGroup(user.id, groupName)
: new AddUserToGroup(user.id, groupName)
);
}

isFeatureToggleActivated(): Observable<boolean> {
return this.featureManagerService.isToggleEnabledForUser('switch-group')
.pipe(map((enabled: boolean) => enabled));
}

filterUserGroup(): Observable<Action> {
return this.actionsSubject$.pipe(
filter(
(action: Action) =>
action.type === UserActionTypes.ADD_USER_TO_GROUP_SUCCESS ||
action.type === UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS
)
);
}
}