Skip to content

Commit

Permalink
Build in warning dialogs for actions that change a users own groups (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
luisa-beerboom authored Sep 4, 2023
1 parent 29b20e3 commit b02f7d0
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 113 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { Id } from 'src/app/domain/definitions/key-types';
Expand All @@ -21,6 +22,7 @@ import { PromptService } from 'src/app/ui/modules/prompt-dialog';
import { ParticipantPdfExportService } from '../../../../export/participant-pdf-export.service';
import { GroupControllerService } from '../../../../modules';
import { getParticipantMinimalSubscriptionConfig } from '../../../../participants.subscription';
import { areGroupsDiminished } from '../../../participant-list/components/participant-list/participant-list.component';

@Component({
selector: `os-participant-detail-view`,
Expand Down Expand Up @@ -284,8 +286,8 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
* (Re)- send an invitation email for this user after confirmation
*/
public async sendInvitationEmail(): Promise<void> {
const title = this.translate.instant(`Sending an invitation email`);
const content = this.translate.instant(`Are you sure you want to send an invitation email to the user?`);
const title = _(`Sending an invitation email`);
const content = _(`Are you sure you want to send an invitation email to the user?`);
if (await this.promptService.open(title, content)) {
this.userController
.sendInvitationEmails([this.user!], this.activeMeetingId)
Expand Down Expand Up @@ -323,7 +325,22 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
.filter(id => !!id)
: []
};
await this.repo.update(payload, this.user!).concat(this.repo.setPresent(isPresent, this.user!)).resolve();
const title = _(`This action will remove you from one or more groups.`);
const content = _(
`This may diminish your ability to do things in this meeting and you may not be able to revert it by youself. Are you sure you want to do this?`
);
if (
!(
this.user.id === this.operator.operatorId &&
areGroupsDiminished(this.operator.user.group_ids(), payload.group_ids, this.activeMeeting)
) ||
(await this.promptService.open(title, content))
) {
await this.repo
.update(payload, this.user!)
.concat(this.repo.setPresent(isPresent, this.user!))
.resolve();
}
} else {
await this.repo.updateSelf(this.personalInfoFormValue, this.user!);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import { UserStateField } from 'src/app/gateways/repositories/users';
import { BaseMeetingListViewComponent } from 'src/app/site/pages/meetings/base/base-meeting-list-view.component';
import { ParticipantControllerService } from 'src/app/site/pages/meetings/pages/participants/services/common/participant-controller.service/participant-controller.service';
import { MeetingComponentServiceCollectorService } from 'src/app/site/pages/meetings/services/meeting-component-service-collector.service';
import { ViewMeeting } from 'src/app/site/pages/meetings/view-models/view-meeting';
import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user';
import { OrganizationSettingsService } from 'src/app/site/pages/organization/services/organization-settings.service';
import { OperatorService } from 'src/app/site/services/operator.service';
import { ChoiceService } from 'src/app/ui/modules/choice-dialog';
import { PromptService } from 'src/app/ui/modules/prompt-dialog';

import { ParticipantCsvExportService } from '../../../../export/participant-csv-export.service';
import { ParticipantPdfExportService } from '../../../../export/participant-pdf-export.service';
Expand All @@ -24,6 +26,14 @@ import { ParticipantListSortService } from '../../services/participant-list-sort

const PARTICIPANTS_LIST_STORAGE_INDEX = `participants`;

export function areGroupsDiminished(oldGroupIds: number[], newGroupIds: number[], activeMeeting: ViewMeeting): boolean {
return (
oldGroupIds
.filter(group => group !== activeMeeting.default_group_id)
.some(id => !(newGroupIds ?? []).includes(id)) && !newGroupIds.includes(activeMeeting.admin_group_id)
);
}

@Component({
selector: `os-participant-list`,
templateUrl: `./participant-list.component.html`,
Expand Down Expand Up @@ -93,6 +103,11 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
private _isElectronicVotingEnabled = false;
private _isUserInScope = true;

private readonly selfGroupRemovalDialogTitle = _(`This action will remove you from one or more groups.`);
private readonly selfGroupRemovalDialogContent = _(
`This may diminish your ability to do things in this meeting and you may not be able to revert it by youself. Are you sure you want to do this?`
);

public constructor(
componentServiceCollector: MeetingComponentServiceCollectorService,
protected override translate: TranslateService,
Expand All @@ -106,7 +121,8 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
private pdfExport: ParticipantPdfExportService,
private infoDialog: ParticipantListInfoDialogService,
private organizationSettingsService: OrganizationSettingsService,
private route: ActivatedRoute
private route: ActivatedRoute,
private prompt: PromptService
) {
super(componentServiceCollector, translate);

Expand Down Expand Up @@ -186,15 +202,23 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
vote_delegated_to_id: user.vote_delegated_to_meeting_user_id()
});

dialogRef.afterClosed().subscribe(result => {
dialogRef.afterClosed().subscribe(async result => {
if (result) {
if (!result.group_ids?.length) {
result.group_ids = [this.activeMeeting!.default_group_id];
}
if (result.vote_delegated_to_id === 0) {
result.vote_delegated_to_id = null;
}
this.repo.update(result, user).resolve();
if (
!(
user.id === this.operator.operatorId &&
areGroupsDiminished(this.operator.user.group_ids(), result.group_ids, this.activeMeeting)
) ||
(await this.prompt.open(this.selfGroupRemovalDialogTitle, this.selfGroupRemovalDialogContent))
) {
this.repo.update(result, user).resolve();
}
}
});
}
Expand Down Expand Up @@ -232,30 +256,41 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
* SelectedRows is only filled with data in multiSelect mode
*/
public async setGroupSelected(): Promise<void> {
const content = this.translate.instant(
`This will add or remove the following groups for all selected participants:`
);
const content = _(`This will add or remove the following groups for all selected participants:`);
const ADD = _(`add group(s)`);
const REMOVE = _(`remove group(s)`);
const choices = [ADD, REMOVE];
const selectedChoice = await this.choiceService.open(content, this.groupsObservable, true, choices);
if (selectedChoice && selectedChoice.ids.length) {
const choosedGroupIds = selectedChoice.ids as Ids;
const chosenGroupIds = selectedChoice.ids as Ids;
if (selectedChoice.action === ADD) {
this.repo
.update(user => {
const nextGroupIds = user.group_ids().filter(id => this.activeMeeting.default_group_id !== id);
return {
id: user.id,
group_ids: nextGroupIds.concat(choosedGroupIds)
group_ids: nextGroupIds.concat(chosenGroupIds)
};
}, ...this.selectedRows)
.resolve();
} else {
} else if (
this.selectedRows.every(
user =>
!(
user.id === this.operator.operatorId &&
areGroupsDiminished(
this.operator.user.group_ids(),
this.operator.user.group_ids().filter(id => !chosenGroupIds.includes(id)),
this.activeMeeting
)
)
) ||
(await this.prompt.open(this.selfGroupRemovalDialogTitle, this.selfGroupRemovalDialogContent))
) {
this.repo
.update(user => {
const nextGroupIds = new Set(user.group_ids());
choosedGroupIds.forEach(id => nextGroupIds.delete(id));
chosenGroupIds.forEach(id => nextGroupIds.delete(id));
return {
id: user.id,
group_ids:
Expand Down Expand Up @@ -319,7 +354,7 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent<ViewU
actions = [_(`natural person`), _(`no natural person`)];
break;
}
const content = this.translate.instant(`Set status for selected participants:`);
const content = _(`Set status for selected participants:`);

const selectedChoice = await this.choiceService.open({ title: content, multiSelect: false, actions });
if (selectedChoice) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ <h2>{{ 'Organization specific information' | translate }}</h2>
<mat-form-field>
<mat-label>{{ 'Administration roles (at organization level)' | translate }}</mat-label>
<mat-select formControlName="organization_management_level">
<mat-option [value]="null">-</mat-option>
<mat-option *ngFor="let level of organizationManagementLevels" [value]="level">
<mat-option [value]="null" *ngIf="!orgaManagementLevelChangeDisabled">-</mat-option>
<mat-option
*ngFor="let level of organizationManagementLevels"
[value]="level"
[disabled]="orgaManagementLevelChangeDisabled"
>
{{ getOmlVerboseName(level) | translate }}
</mat-option>
</mat-select>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { KeyValue } from '@angular/common';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { Id } from 'src/app/domain/definitions/key-types';
import { getOmlVerboseName, OML, OMLMapping } from 'src/app/domain/definitions/organization-permission';
Expand Down Expand Up @@ -35,7 +36,15 @@ type ParticipationTableMeetingDataRow = { meeting_name: string; group_names: str
})
export class AccountDetailComponent extends BaseComponent implements OnInit {
public get organizationManagementLevels(): string[] {
return Object.values(OML).filter((level: OML) => this.operator.hasOrganizationPermissions(level));
return Object.values(OML).filter(
(level: OML) =>
this.operator.hasOrganizationPermissions(level) &&
!(this.orgaManagementLevelChangeDisabled && level !== this.user.organization_management_level)
);
}

public get orgaManagementLevelChangeDisabled(): boolean {
return this.user?.id === this.operator.operatorId && this.operator.isSuperAdmin;
}

@ViewChild(UserDetailViewComponent, { static: false })
Expand Down Expand Up @@ -268,8 +277,21 @@ export class AccountDetailComponent extends BaseComponent implements OnInit {

private async updateUser(): Promise<void> {
const payload = this.getPartialUserPayload();
await this.userController.update(payload, this.user!).resolve();
this.router.navigate([`..`], { relativeTo: this.route });
if (
!(
this.user.id === this.operator.operatorId &&
this.operator.user.organization_management_level !== payload.organization_management_level
) ||
(await this.promptService.open(
_(`This action will diminish your organization management level`),
_(
`This will diminish your ability to do things on the organization level and you will not be able to revert this yourself.`
)
))
) {
await this.userController.update(payload, this.user!).resolve();
this.router.navigate([`..`], { relativeTo: this.route });
}
}

private getPartialUserPayload(): any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ export class AccountListComponent extends BaseListViewComponent<ViewUser> {
if (result.action === ADD) {
this.controller.bulkAddUserToMeeting(this.selectedRows, ...result.items).resolve();
} else {
this.controller.bulkRemoveUserFromMeeting(this.selectedRows, ...result.items).resolve();
this.controller.bulkRemoveUserFromMeeting(this.selectedRows, ...result.items).then(action => {
if (action) {
action.resolve();
}
});
}
}
}
Expand Down
Loading

0 comments on commit b02f7d0

Please sign in to comment.