Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/app/core/services/data/outbreak.data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ export class OutbreakDataService {
);
}

/**
* Retrieve outbreaks (minimal fields) for user management screens, regardless of
* the current user's outbreak access restrictions. Gated server-side by the
* 'user_list' permission. Used to resolve names of outbreaks assigned to other
* users that are outside the current user's own access scope.
*/
getOutbreaksListForUserManagement(
queryBuilder: RequestQueryBuilder = new RequestQueryBuilder()
): Observable<OutbreakModel[]> {
const filter = queryBuilder.buildQuery();
return this.modelHelper.mapObservableListToModel(
this.http.get(`outbreaks/for-user-management?filter=${filter}`),
OutbreakModel
);
}

/**
* Retrieve the list of Outbreaks
* @returns {Observable<OutbreakModel[]>}
Expand Down
15 changes: 12 additions & 3 deletions src/app/core/services/resolvers/data/outbreak.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ export class OutbreakDataResolver implements IMapResolverV2<OutbreakModel> {
* Retrieve data
*/
resolve(route: ActivatedRouteSnapshot): Observable<IResolverV2ResponseModel<OutbreakModel>> {
// user management screens need to resolve the names of outbreaks assigned to
// other users even when they are outside the current user's access scope;
// this is gated server-side by the 'user_list' permission
const loadAllForUserManagement = !!route.data?.outbreakLoadAllForUserManagement;

// user doesn't have rights ?
if (!OutbreakModel.canList(this.authDataService.getAuthenticatedUser())) {
if (
!loadAllForUserManagement &&
!OutbreakModel.canList(this.authDataService.getAuthenticatedUser())
) {
return of({
list: [],
map: {},
Expand All @@ -51,8 +59,9 @@ export class OutbreakDataResolver implements IMapResolverV2<OutbreakModel> {
.by('name', RequestSortDirection.ASC);

// retrieve records
return this.outbreakDataService
.getOutbreaksList(qb)
return (loadAllForUserManagement ?
this.outbreakDataService.getOutbreaksListForUserManagement(qb) :
this.outbreakDataService.getOutbreaksList(qb))
.pipe(
map((data) => {
// construct map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,68 +141,44 @@ export class OutbreakListComponent extends ListComponent<OutbreakModel, IV2Colum
iconTooltip: 'LNG_PAGE_LIST_OUTBREAKS_ACTION_SET_ACTIVE',
action: {
click: (item: OutbreakModel): void => {
// show confirm dialog
this.dialogV2Service.showConfirmDialog({
config: {
title: {
get: () => 'LNG_COMMON_LABEL_ACTIVE',
data: () => ({
name: item.name
})
},
message: {
get: () => 'LNG_DIALOG_CONFIRM_MAKE_OUTBREAK_ACTIVE',
data: () => ({
name: item.name
})
// show loading
const loading = this.dialogV2Service.showLoadingDialog();

// modify outbreak
this.userDataService
.modifyUser(
this.authUser.id,
{
activeOutbreakId: item.id
}
}
}).subscribe((response) => {
// canceled ?
if (response.button.type === IV2BottomDialogConfigButtonType.CANCEL) {
// finished
return;
}

// show loading
const loading = this.dialogV2Service.showLoadingDialog();

// modify outbreak
this.userDataService
.modifyUser(
this.authUser.id,
{
activeOutbreakId: item.id
}
)
.pipe(
catchError((err) => {
this.toastV2Service.error(err);
return throwError(err);
})
)
.subscribe(() => {
// reload user data to save the new active outbreak
this.authDataService
.reloadAndPersistAuthUser()
.subscribe((authenticatedUser) => {
this.authUser = authenticatedUser.user;
this.outbreakDataService.checkActiveSelectedOutbreak();

// refresh list of top nav outbreaks
TopnavComponent.REFRESH_OUTBREAK_LIST();

// success
this.toastV2Service.success('LNG_PAGE_LIST_OUTBREAKS_ACTION_SET_ACTIVE_SUCCESS_MESSAGE');

// hide loading
loading.close();

// reload data
this.needsRefreshList(true);
});
});
});
)
.pipe(
catchError((err) => {
this.toastV2Service.error(err);
return throwError(err);
})
)
.subscribe(() => {
// reload user data to save the new active outbreak
this.authDataService
.reloadAndPersistAuthUser()
.subscribe((authenticatedUser) => {
this.authUser = authenticatedUser.user;
this.outbreakDataService.checkActiveSelectedOutbreak();

// refresh list of top nav outbreaks
TopnavComponent.REFRESH_OUTBREAK_LIST();

// success
this.toastV2Service.success('LNG_PAGE_LIST_OUTBREAKS_ACTION_SET_ACTIVE_SUCCESS_MESSAGE');

// hide loading
loading.close();

// reload data
this.needsRefreshList(true);
});
});
}
},
cssClasses: (item: OutbreakModel): string => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ToastV2Service } from '../../../../core/services/helper/toast-v2.servic
import { I18nService } from '../../../../core/services/helper/i18n.service';
import { V2SideDialogConfigInputType } from '../../../../shared/components-v2/app-side-dialog-v2/models/side-dialog-config.model';
import { LocalizationHelper } from '../../../../core/helperClasses/localization-helper';
import { IV2BottomDialogConfigButtonType } from '../../../../shared/components-v2/app-bottom-dialog-v2/models/bottom-dialog-config.model';

/**
* Component
Expand All @@ -40,6 +41,7 @@ import { LocalizationHelper } from '../../../../core/helperClasses/localization-
export class UserCreateViewModifyComponent extends CreateViewModifyComponent<UserModel> implements OnDestroy {
// data
private _passwordConfirm: string;
private _originalOutbreakIds: string[] = [];

/**
* Constructor
Expand Down Expand Up @@ -96,7 +98,9 @@ export class UserCreateViewModifyComponent extends CreateViewModifyComponent<Use
/**
* Data initialized
*/
protected initializedData(): void {}
protected initializedData(): void {
this._originalOutbreakIds = [...(this.itemData.outbreakIds ?? [])];
}

/**
* Initialize page title
Expand Down Expand Up @@ -374,11 +378,8 @@ export class UserCreateViewModifyComponent extends CreateViewModifyComponent<Use
description: () => 'LNG_USER_FIELD_LABEL_AVAILABLE_OUTBREAKS_DESCRIPTION',
value: {
get: () => this.itemData.outbreakIds,
set: (value) => {
// set data
set: (value: string[]) => {
this.itemData.outbreakIds = value;

// update visible active outbreaks
(tab.nameToInput.activeOutbreakId as ICreateViewModifyV2TabInputSingleSelect).options = this.getAllowedActiveOutbreaks();
}
},
Expand Down Expand Up @@ -558,35 +559,49 @@ export class UserCreateViewModifyComponent extends CreateViewModifyComponent<Use
// cleanup
delete data.passwordConfirm;

// create / modify
(
type === CreateViewModifyV2ActionType.CREATE ?
this.userDataService.createUser(
data
) :
this.userDataService.modifyUser(
this.itemData.id,
data
)
).pipe(
catchError((err) => {
// show error
finished(err, undefined);

// finished
return throwError(err);
})
).subscribe((outbreak) => {
// display message
this.toastV2Service.success(
// check if any previously saved outbreaks are being removed
const removedOutbreaks = this._originalOutbreakIds.filter(
(id) => !(data.outbreakIds ?? []).includes(id)
);

const doSave = () => {
// create / modify
(
type === CreateViewModifyV2ActionType.CREATE ?
'LNG_PAGE_CREATE_USER_ACTION_CREATE_USER_SUCCESS_MESSAGE' :
'LNG_PAGE_MODIFY_USER_ACTION_MODIFY_USER_SUCCESS_MESSAGE'
);
this.userDataService.createUser(data) :
this.userDataService.modifyUser(this.itemData.id, data)
).pipe(
catchError((err) => {
finished(err, undefined);
return throwError(err);
})
).subscribe((outbreak) => {
this.toastV2Service.success(
type === CreateViewModifyV2ActionType.CREATE ?
'LNG_PAGE_CREATE_USER_ACTION_CREATE_USER_SUCCESS_MESSAGE' :
'LNG_PAGE_MODIFY_USER_ACTION_MODIFY_USER_SUCCESS_MESSAGE'
);
finished(undefined, outbreak);
});
};

// hide loading & redirect
finished(undefined, outbreak);
});
if (type === CreateViewModifyV2ActionType.UPDATE && removedOutbreaks.length) {
this.dialogV2Service.showConfirmDialog({
config: {
title: { get: () => 'LNG_USER_FIELD_LABEL_REMOVE_OUTBREAK_CONFIRM_TITLE' },
message: { get: () => 'LNG_USER_FIELD_LABEL_REMOVE_OUTBREAK_CONFIRM' }
}
}).subscribe((response) => {
if (response.button.type === IV2BottomDialogConfigButtonType.CANCEL) {
finished(undefined, undefined);
return;
}
doSave();
});
return;
}

doSave();
};
}

Expand Down
8 changes: 6 additions & 2 deletions src/app/features/user/user.module.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ const routes: Routes = [
permissions: [
PERMISSION.USER_LIST
],
outbreakIncludeDeleted: true
outbreakIncludeDeleted: true,
// resolve names of outbreaks assigned to other users even when outside the
// current user's access scope (gated server-side by 'user_list' permission)
outbreakLoadAllForUserManagement: true
},
resolve: {
createdOn: CreatedOnResolver,
Expand Down Expand Up @@ -83,7 +86,8 @@ const routes: Routes = [
PERMISSION.USER_VIEW
],
action: CreateViewModifyV2Action.VIEW,
outbreakIncludeDeleted: true
outbreakIncludeDeleted: true,
outbreakLoadAllForUserManagement: true
}
},
// Edit user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
fxLayout="row wrap"
class="gd-form-select-multiple-v2-panel-inner">
<ng-container
*ngFor="let selectedValue of value; let selectedIndex = index">
*ngFor="let selectedValue of validSelectedValues; let selectedIndex = index">

<!-- separator -->
<ng-container
Expand Down Expand Up @@ -188,7 +188,7 @@
<ng-container
*ngIf="value?.length > 0; else noValue">
<ng-container
*ngFor="let selectedValue of value; let selectedIndex = index">
*ngFor="let selectedValue of validSelectedValues; let selectedIndex = index">

<!-- separator -->
<ng-container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ export class AppFormSelectMultipleV2Component
return this.allOptions;
}

get validSelectedValues(): string[] {
return (this.value || []).filter((v) => !!this.allOptionsMap[v]);
}

// allow disabled options to be selected ?
@Input() allowDisabledToBeSelected: boolean = false;

Expand Down