Skip to content

Commit

Permalink
feat: delete B2B users in organization-management (#258, #310)
Browse files Browse the repository at this point in the history
Closes #258
  • Loading branch information
Sebastian-Haehnlein authored and dhhyi committed Jul 3, 2020
1 parent 3185084 commit d8d8523
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Store, select } from '@ngrx/store';
import { B2bUser } from '../models/b2b-user/b2b-user.model';
import {
addUser,
deleteUser,
getSelectedUser,
getUsers,
getUsersError,
Expand Down Expand Up @@ -41,4 +42,8 @@ export class OrganizationManagementFacade {
})
);
}

deleteUser(login: string) {
this.store.dispatch(deleteUser({ login }));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,30 @@ <h1>
<a [routerLink]="[user.login]">{{ user.name }}</a>
</div>
<div class="col-3 list-item text-right">
<!---TODO: Delete user-->
<div class="float-right">
<a
class="btn-tool"
title="{{
'account.user.delete_user_dialog.header' | translate: { '0': user.firstName, '1': user.lastName }
}}"
(click)="modalDialog.show(user)"
>
<fa-icon [icon]="['fas', 'trash-alt']"></fa-icon>
</a>

<ish-modal-dialog
#modalDialog
[options]="{
titleText:
'account.user.delete_user_dialog.header' | translate: { '0': user.firstName, '1': user.lastName },
confirmText: 'account.user.delete_user_dialog.delete_button.text' | translate,
rejectText: 'account.user.delete_user_dialog.cancel_button.text' | translate
}"
(confirmed)="deleteUser($event)"
>
<p>{{ 'account.user.delete_user_dialog.are_you_sure_paragraph' | translate }}</p>
</ish-modal-dialog>
</div>
</div>
</div>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateModule } from '@ngx-translate/core';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';

import { ErrorMessageComponent } from 'ish-shared/components/common/error-message/error-message.component';
import { LoadingComponent } from 'ish-shared/components/common/loading/loading.component';
import { ModalDialogComponent } from 'ish-shared/components/common/modal-dialog/modal-dialog.component';

import { OrganizationManagementFacade } from '../../facades/organization-management.facade';
import { B2bUser } from '../../models/b2b-user/b2b-user.model';
Expand All @@ -29,7 +31,13 @@ describe('Users Page Component', () => {

TestBed.configureTestingModule({
imports: [RouterTestingModule, TranslateModule.forRoot()],
declarations: [MockComponent(ErrorMessageComponent), MockComponent(LoadingComponent), UsersPageComponent],
declarations: [
MockComponent(ErrorMessageComponent),
MockComponent(FaIconComponent),
MockComponent(LoadingComponent),
MockComponent(ModalDialogComponent),
UsersPageComponent,
],
providers: [{ provide: OrganizationManagementFacade, useFactory: () => instance(organizationManagementFacade) }],
}).compileComponents();
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ export class UsersPageComponent implements OnInit {
this.error$ = this.organizationManagementFacade.usersError$;
this.loading$ = this.organizationManagementFacade.usersLoading$;
}

deleteUser(user: B2bUser) {
this.organizationManagementFacade.deleteUser(user.login);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('Users Service', () => {
apiService = mock(ApiService);
when(apiService.get(anything())).thenReturn(of(true));
when(apiService.resolveLinks()).thenReturn(() => of([]));
when(apiService.delete(anything())).thenReturn(of(true));

TestBed.configureTestingModule({
providers: [{ provide: ApiService, useFactory: () => instance(apiService) }],
Expand Down Expand Up @@ -44,6 +45,18 @@ describe('Users Service', () => {
usersService.getUser('pmiller@test.intershop.de').subscribe(() => {
verify(apiService.get(anything())).once();
expect(capture(apiService.get).last()).toMatchInlineSnapshot(`
Array [
"customers/-/users/pmiller@test.intershop.de",
]
`);
done();
});
});

it('should call delete method of customer API when delete user', done => {
usersService.deleteUser('pmiller@test.intershop.de').subscribe(() => {
verify(apiService.delete(anything())).once();
expect(capture(apiService.delete).last()).toMatchInlineSnapshot(`
Array [
"customers/-/users/pmiller@test.intershop.de",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,17 @@ export class UsersService {
})
.pipe(map(B2bUserMapper.fromData));
}

/**
* Deletes the data of a b2b user. The current user is expected to have user management permission.
* @param login The login of the user.
* @returns The user.
*/
deleteUser(login: string) {
if (!login) {
return throwError('deleteUser() called without customerItemUserKey/login');
}

return this.apiService.delete(`customers/-/users/${login}`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ export const updateUserFail = createAction('[Users API] Update User Fail', httpE
export const updateUserSuccess = createAction('[Users API] Update User Success', payload<{ user: B2bUser }>());

export const resetUsers = createAction('[Users] Reset Users');

export const deleteUser = createAction('[Users API] Delete User', payload<{ login: string }>());

export const deleteUserFail = createAction('[Users API] Delete User Fail', httpError());

export const deleteUserSuccess = createAction('[Users API] Delete User Success', payload<{ login: string }>());
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
addUser,
addUserFail,
addUserSuccess,
deleteUser,
loadUsers,
loadUsersFail,
updateUser,
Expand Down Expand Up @@ -50,6 +51,8 @@ describe('Users Effects', () => {
when(usersService.getUser(anything())).thenReturn(of(users[0]));
when(usersService.addUser(anything())).thenReturn(of(users[0]));
when(usersService.updateUser(anything())).thenReturn(of(users[0]));
when(usersService.getUsers()).thenReturn(of(users));
when(usersService.deleteUser(anything())).thenReturn(of(true));

TestBed.configureTestingModule({
declarations: [DummyComponent],
Expand Down Expand Up @@ -211,4 +214,29 @@ describe('Users Effects', () => {
});
});
});

describe('deleteUser$', () => {
const login = 'pmiller@test.intershop.de';

it('should call the service for delete user', done => {
actions$ = of(deleteUser({ login }));

effects.deleteUser$.subscribe(() => {
verify(usersService.deleteUser(anything())).once();
done();
});
});

it('should delete user when triggered', done => {
actions$ = of(deleteUser({ login }));

effects.deleteUser$.subscribe(action => {
expect(action).toMatchInlineSnapshot(`
[Users API] Delete User Success:
login: "pmiller@test.intershop.de"
`);
done();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { Customer } from 'ish-core/models/customer/customer.model';
import { displaySuccessMessage } from 'ish-core/store/core/messages';
import { selectRouteParam } from 'ish-core/store/core/router';
import { getLoggedInCustomer, logoutUser } from 'ish-core/store/customer/user';
import { mapErrorToAction, mapToPayload, whenTruthy } from 'ish-core/utils/operators';
import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators';

import { UsersService } from '../../services/users/users.service';

import {
addUser,
addUserFail,
addUserSuccess,
deleteUser,
deleteUserFail,
deleteUserSuccess,
loadUserFail,
loadUserSuccess,
loadUsers,
Expand Down Expand Up @@ -110,6 +113,18 @@ export class UsersEffects {

resetUsersAfterLogout$ = createEffect(() => this.actions$.pipe(ofType(logoutUser), mapTo(resetUsers())));

deleteUser$ = createEffect(() =>
this.actions$.pipe(
ofType(deleteUser),
mapToPayloadProperty('login'),
exhaustMap(login =>
this.usersService
.deleteUser(login)
.pipe(map(() => deleteUserSuccess({ login }), mapErrorToAction(deleteUserFail)))
)
)
);

private navigateToParent(): void {
// find current ActivatedRoute by following first activated children
let currentRoute = this.router.routerState.root;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
addUser,
addUserFail,
addUserSuccess,
deleteUser,
deleteUserFail,
deleteUserSuccess,
loadUserFail,
loadUserSuccess,
loadUsers,
Expand Down Expand Up @@ -37,8 +40,8 @@ const initialState: UsersState = usersAdapter.getInitialState({

export const usersReducer = createReducer(
initialState,
setLoadingOn(loadUsers, addUser, updateUser),
setErrorOn(loadUsersFail, loadUserFail, addUserFail, updateUserFail),
setLoadingOn(loadUsers, addUser, updateUser, deleteUser),
setErrorOn(loadUsersFail, loadUserFail, addUserFail, updateUserFail, deleteUserFail),
on(loadUsersSuccess, (state: UsersState, action) => {
const { users } = action.payload;

Expand Down Expand Up @@ -75,5 +78,14 @@ export const usersReducer = createReducer(
error: undefined,
};
}),
on(deleteUserSuccess, (state: UsersState, action) => {
const { login } = action.payload;

return {
...usersAdapter.removeOne(login, state),
loading: false,
error: undefined,
};
}),
on(resetUsers, () => initialState)
);

0 comments on commit d8d8523

Please sign in to comment.