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 @@ -4,8 +4,7 @@
class="table table-sm table-bordered table-striped mb-0"
datatable
[dtTrigger]="dtTrigger"
[dtOptions]="dtOptions"
>
[dtOptions]="dtOptions">
<thead class="thead-blue">
<tr class="d-flex flex-wrap">
<th class="col-4">User Email</th>
Expand All @@ -21,15 +20,13 @@
<td class="col-3 text-center">
<ui-switch
size="small"
(change)="switchGroup('time-tracker-admin', user)"
[checked]="user.groups.includes('time-tracker-admin')"
></ui-switch>
(change)="switchGroup('time-tracker-admin', user); updateRole(ROLES.admin, user, $event);"
[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>
[checked]="user.groups.includes('time-tracker-tester')"></ui-switch>
test
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ 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,
AddUserToGroup,
RemoveUserFromGroup,
} from '../../store';
import { UserActionTypes, UserState, LoadUsers, AddUserToGroup, RemoveUserFromGroup } from '../../store';
import { ActionsSubject } from '@ngrx/store';
import { DataTablesModule } from 'angular-datatables';
import { GrantUserRole, RevokeUserRole } from '../../store/user.actions';
import { ROLES } from '../../../../../environments/environment';

describe('UsersListComponent', () => {
let component: UsersListComponent;
Expand Down Expand Up @@ -39,10 +35,7 @@ describe('UsersListComponent', () => {
TestBed.configureTestingModule({
imports: [NgxPaginationModule, DataTablesModule],
declarations: [UsersListComponent],
providers: [
provideMockStore({ initialState: state }),
{ provide: ActionsSubject, useValue: actionSub },
],
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
}).compileComponents();
})
);
Expand Down Expand Up @@ -100,10 +93,7 @@ describe('UsersListComponent', () => {
});
});

const AddGroupTypes = [
{ groupName: 'time-tracker-admin' },
{ groupName: 'time-tracker-tester' }
];
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`, () => {
Expand Down Expand Up @@ -179,6 +169,66 @@ describe('UsersListComponent', () => {
expect(component.dtElement.dtInstance.then).toHaveBeenCalled();
});

it('When the toggle is enabled, the role must be added to the user ', () => {
const availableRoles = [
{
name: 'admin',
value: 'time-tracker-admin',
},
{
name: 'test',
value: 'time-tracker-tester',
},
];

spyOn(store, 'dispatch');

const user = {
id: 'no-matter-id',
name: 'no-matter-name',
email: 'no-matter-email',
roles: [],
};

availableRoles.forEach((role) => {
const isToggleEnabled = true;
component.updateRole(role, user, isToggleEnabled);
expect(store.dispatch).toHaveBeenCalledWith(new GrantUserRole(user.id, role.name));
});
});

it('When the toggle is disabled, the role must be removed from the user', () => {
const availableRoles = [
{
name: 'admin',
value: 'time-tracker-admin',
},
{
name: 'test',
value: 'time-tracker-tester',
},
];

spyOn(store, 'dispatch');

const user = {
id: 'no-matter-id',
name: 'no-matter-name',
email: 'no-matter-email',
roles: ['time-tracker-admin', 'time-tracker-tester'],
};

availableRoles.forEach((role) => {
const isToggleEnabled = false;
component.updateRole(role, user, isToggleEnabled);
expect(store.dispatch).toHaveBeenCalledWith(new RevokeUserRole(user.id, role.name));
});
});

it('When we call ROLES variable should return available roles', () => {
expect(component.ROLES).toEqual(ROLES);
});

afterEach(() => {
component.dtTrigger.unsubscribe();
component.loadUsersSubscription.unsubscribe();
Expand Down
23 changes: 13 additions & 10 deletions src/app/modules/users/components/users-list/users-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { GrantUserRole, RevokeUserRole } from './../../store/user.actions';
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
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 { ROLES } from 'src/environments/environment';
import { User } from '../../models/users';
import {
LoadUsers,
UserActionTypes,
AddUserToGroup,
RemoveUserFromGroup,
} from '../../store/user.actions';
import { LoadUsers, UserActionTypes, AddUserToGroup, RemoveUserFromGroup } from '../../store/user.actions';
import { getIsLoading } from '../../store/user.selectors';

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

constructor(
private store: Store<User>,
private actionsSubject$: ActionsSubject,
) {
public get ROLES() {
return ROLES;
}

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

Expand Down Expand Up @@ -77,6 +75,11 @@ export class UsersListComponent implements OnInit, OnDestroy, AfterViewInit {
);
}

updateRole(role: { name: string; value: string }, user: User, isToggleEnabled: boolean) {
const action = isToggleEnabled ? new GrantUserRole(user.id, role.name) : new RevokeUserRole(user.id, role.name);
this.store.dispatch(action);
}

filterUserGroup(): Observable<Action> {
return this.actionsSubject$.pipe(
filter(
Expand Down
42 changes: 42 additions & 0 deletions src/app/modules/users/store/user.actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,46 @@ describe('UserActions', () => {

expect(action.type).toEqual(actions.UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL);
});

it('GrantUserRole type is UserActionTypes.GRANT_USER_ROLE', () => {
const userId = 'no-matter-id';
const roleId = 'no-matter-role-id';
const action = new actions.GrantUserRole(userId, roleId);

expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE);
});

it('GrantUserRoleSuccess type is UserActionTypes.GRANT_USER_ROLE_SUCCESS', () => {
const user: User = { id: 'id', email: 'email', name: 'name' };
const action = new actions.GrantUserRoleSuccess(user);

expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE_SUCCESS);
});

it('GrantUserRoleFail type is UserActionTypes.GRANT_USER_ROLE_FAIL', () => {
const action = new actions.GrantUserRoleFail('error');

expect(action.type).toEqual(actions.UserActionTypes.GRANT_USER_ROLE_FAIL);
});

it('RevokeUserRole type is UserActionTypes.REVOKE_USER_ROLE', () => {
const userId = 'no-matter-id';
const roleId = 'no-matter-role-id';
const action = new actions.RevokeUserRole(userId, roleId);

expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE);
});

it('RevokeUserRoleSuccess type is UserActionTypes.REVOKE_USER_ROLE_SUCCESS', () => {
const user: User = { id: 'id', email: 'email', name: 'name' };
const action = new actions.RevokeUserRoleSuccess(user);

expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE_SUCCESS);
});

it('RevokeUserRoleFail type is UserActionTypes.REVOKE_USER_ROLE_FAIL', () => {
const action = new actions.RevokeUserRoleFail('error');

expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE_FAIL);
});
});
62 changes: 52 additions & 10 deletions src/app/modules/users/store/user.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ export enum UserActionTypes {
REMOVE_USER_FROM_GROUP = '[User] REMOVE_USER_FROM_GROUP',
REMOVE_USER_FROM_GROUP_SUCCESS = '[User] REMOVE_USER_FROM_GROUP_SUCCESS',
REMOVE_USER_FROM_GROUP_FAIL = '[User] REMOVE_USER_FROM_GROUP_FAIL',
DEFAULT_USER = '[USER] DEFAULT_USER',
GRANT_USER_ROLE = '[User] GRANT_USER_ROLE',
GRANT_USER_ROLE_SUCCESS = '[User] GRANT_USER_ROLE_SUCCESS',
GRANT_USER_ROLE_FAIL = '[User] GRANT_USER_ROLE_FAIL',
REVOKE_USER_ROLE = '[User] REVOKE_USER_ROLE',
REVOKE_USER_ROLE_SUCCESS = '[User] REVOKE_USER_ROLE_SUCCESS',
REVOKE_USER_ROLE_FAIL = '[User] REVOKE_USER_ROLE_FAIL',
DEFAULT_USER = '[User] DEFAULT_USER',
}

export class LoadUsers implements Action {
Expand All @@ -20,42 +26,72 @@ export class LoadUsers implements Action {

export class LoadUsersSuccess implements Action {
readonly type = UserActionTypes.LOAD_USERS_SUCCESS;
constructor(readonly payload: User[]) { }
constructor(readonly payload: User[]) {}
}

export class LoadUsersFail implements Action {
public readonly type = UserActionTypes.LOAD_USERS_FAIL;
constructor(public error: string) { }
constructor(public error: string) {}
}

export class AddUserToGroup implements Action {
public readonly type = UserActionTypes.ADD_USER_TO_GROUP;
constructor(public userId: string, public groupName: string) { }
constructor(public userId: string, public groupName: string) {}
}

export class AddUserToGroupSuccess implements Action {
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_SUCCESS;
constructor(readonly payload: User) { }
constructor(readonly payload: User) {}
}

export class AddUserToGroupFail implements Action {
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_FAIL;
constructor(public error: string) { }
constructor(public error: string) {}
}

export class RemoveUserFromGroup implements Action {
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP;
constructor(public userId: string, public groupName: string) { }
constructor(public userId: string, public groupName: string) {}
}

export class RemoveUserFromGroupSuccess implements Action {
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS;
constructor(readonly payload: User) { }
constructor(readonly payload: User) {}
}

export class RemoveUserFromGroupFail implements Action {
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL;
constructor(public error: string) { }
constructor(public error: string) {}
}

export class GrantUserRole implements Action {
public readonly type = UserActionTypes.GRANT_USER_ROLE;
constructor(public userId: string, public roleId: string) {}
}

export class GrantUserRoleSuccess implements Action {
public readonly type = UserActionTypes.GRANT_USER_ROLE_SUCCESS;
constructor(readonly payload: User) {}
}

export class GrantUserRoleFail implements Action {
public readonly type = UserActionTypes.GRANT_USER_ROLE_FAIL;
constructor(public error: string) {}
}

export class RevokeUserRole implements Action {
public readonly type = UserActionTypes.REVOKE_USER_ROLE;
constructor(public userId: string, public roleId: string) {}
}

export class RevokeUserRoleSuccess implements Action {
public readonly type = UserActionTypes.REVOKE_USER_ROLE_SUCCESS;
constructor(readonly payload: User) {}
}

export class RevokeUserRoleFail implements Action {
public readonly type = UserActionTypes.REVOKE_USER_ROLE_FAIL;
constructor(public error: string) {}
}

export class DefaultUser implements Action {
Expand All @@ -72,4 +108,10 @@ export type UserActions =
| AddUserToGroupFail
| RemoveUserFromGroup
| RemoveUserFromGroupSuccess
| RemoveUserFromGroupFail;
| RemoveUserFromGroupFail
| GrantUserRole
| GrantUserRoleSuccess
| GrantUserRoleFail
| RevokeUserRole
| RevokeUserRoleSuccess
| RevokeUserRoleFail;
Loading