Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock participant out of meeting #3815

Merged
merged 63 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9d2fadf
Add lock out fields to detail view, dummy logic
reiterl Jul 4, 2024
c8a80f9
Draft version of the lockedout in participant-list
reiterl Jul 4, 2024
95f478b
Add locked out filter and sort
reiterl Jul 4, 2024
c7320ab
Small change to sort service config
reiterl Jul 4, 2024
ee85f89
hide is_locked_out sort if missing perms
reiterl Jul 4, 2024
ddde755
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Jul 10, 2024
0d4e2b3
Update participant-list, show lockout with icon and warn color
reiterl Jul 10, 2024
0b7b320
Update menu entries in participant-list
reiterl Jul 10, 2024
1e69803
Disallow to lockout a user with OMLs.
reiterl Jul 11, 2024
1cf375d
Merge branch 'main' into 3793-lock-out-of-meeting
bastianjoel Jul 12, 2024
a2c3a96
Merge commit '55a87551584f4b40c255737a1eb488ba4d0f5be7' into 3793-loc…
reiterl Jul 12, 2024
ec3cbfe
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Jul 12, 2024
60b8d0e
Merge branch '3793-lock-out-of-meeting' of ssh://github.com/reiterl/o…
reiterl Jul 12, 2024
7b0555f
Draft version of the meeting_user locked_out logic
reiterl Jul 31, 2024
189ee91
Fix layout
reiterl Jul 31, 2024
43c488c
Add locked_out logic to participant-detail-view
reiterl Jul 31, 2024
250a75f
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Jul 31, 2024
ce16255
Fix layout
reiterl Jul 31, 2024
74fc068
Fix multiselect lockout setState, toggleLockout, layout
reiterl Aug 1, 2024
ba09c25
Use lockout in the participant export, too
reiterl Aug 1, 2024
b4213e4
Add three new cases to isLockedOutToggleDisabled
reiterl Aug 6, 2024
44a6be8
Bulk set_lockout excludes the disallowed users and show warning
reiterl Aug 7, 2024
556ba83
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Aug 7, 2024
45247a8
Merge branch 'main' into 3793-lock-out-of-meeting
Elblinator Aug 13, 2024
49b99ef
In participant-detail-view move locked out next to is_present
reiterl Aug 14, 2024
89354d6
Change icon
reiterl Aug 14, 2024
5632c79
Update enable save button in the particpant-detail-view
reiterl Aug 14, 2024
53cf9d5
Add lockout and canManage warning in participant edit
reiterl Aug 14, 2024
1aa0293
Add disableLockoutCheckbox to lockout checkbox
reiterl Aug 15, 2024
dd935de
Show locked out in isMobile view
reiterl Aug 15, 2024
364d216
Add prompt, menu item color and different label
reiterl Aug 15, 2024
564b186
Move inactive icon under lockout in participant-list
reiterl Aug 15, 2024
773b0f9
Merge branch '3793-lock-out-of-meeting' of ssh://github.com/reiterl/o…
reiterl Aug 15, 2024
2292b55
Merge branch 'main' into 3793-lock-out-of-meeting
bastianjoel Aug 22, 2024
2d28191
Use computed properties in participant-detail-view
reiterl Sep 2, 2024
65e6d6c
Rename some computed properties
reiterl Sep 2, 2024
84a4d1e
Use group.hasPermission in filterForLockedOut for cleanup
reiterl Sep 2, 2024
174ce3e
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Sep 2, 2024
f7aa53c
Update permission help text
reiterl Sep 2, 2024
9fb9a4f
Small renaming of computed properties
reiterl Sep 4, 2024
7a7402b
Hide locked out icon in mobile view
reiterl Sep 11, 2024
8e44355
Fix login for locked out user without meetings
bastianjoel Sep 12, 2024
636abb0
Use ensuredMeetingIds for in meeting checks
bastianjoel Sep 12, 2024
d66d679
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Sep 16, 2024
2d854cb
Merge branch 'main' into 3793-lock-out-of-meeting
rrenkert Sep 16, 2024
dcb046d
Update lockout perms, fix disabled error and self in detail view
reiterl Sep 19, 2024
a0a9f4c
Add locked out field to participant wizard
reiterl Sep 20, 2024
99b3cab
Rename the multi set lockout buttons
reiterl Sep 20, 2024
5f5224d
Fix logo navigatio after locked out
bastianjoel Sep 20, 2024
18eead2
Redirect from dashboard
bastianjoel Sep 20, 2024
ef50902
Move locked out menu entry above delete
reiterl Sep 24, 2024
3c616f5
Update participant-detail-view layout, warn icon
reiterl Sep 24, 2024
fdfb47b
Align locked out icon and make only icon red
reiterl Sep 24, 2024
3122a45
Changed the locked out icon to 'visibility_off'
reiterl Sep 25, 2024
4a1a388
Fix logo link
bastianjoel Sep 26, 2024
68e4767
Use translate attribute for error msgs
reiterl Sep 26, 2024
4943b95
Small code cleanup, one method of changeEvent, rm check const
reiterl Sep 26, 2024
5b29003
Change get-property into method again
reiterl Sep 26, 2024
e7849d9
Move properties above constructor
reiterl Sep 26, 2024
0ea870e
Move committee/committee_manager to ParticipantList fieldset
reiterl Sep 26, 2024
0a0d5ba
Merge branch 'main' into 3793-lock-out-of-meeting
reiterl Sep 26, 2024
f30cfef
Small code cleanup
reiterl Sep 26, 2024
fa57bba
Change margin-left
Elblinator Sep 26, 2024
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
4 changes: 3 additions & 1 deletion client/src/app/domain/definitions/permission.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ Meeting specific information: Structure level, Group, Participant number, About
},
{
display_name: _(`Can see sensitive data`),
help_text: _(`Can see email, username and SSO identification of all participants.`),
help_text: _(
`Can see email, username, membership number, SSO identification and locked out state of all participants.`
),
value: Permission.userCanSeeSensitiveData
},
{
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/domain/models/meeting-users/meeting-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class MeetingUser extends BaseDecimalModel<MeetingUser> {
public readonly number!: string;
public readonly about_me!: string;
public readonly vote_weight!: number;
public readonly locked_out!: boolean;

public user_id!: Id;
public meeting_id!: Id;
Expand Down Expand Up @@ -42,6 +43,7 @@ export class MeetingUser extends BaseDecimalModel<MeetingUser> {
`number`,
`about_me`,
`vote_weight`,
`locked_out`,
`user_id`,
`meeting_id`,
`personal_note_ids`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class MeetingUserRepositoryService extends BaseMeetingRelatedRepository<V
`vote_weight`,
`comment`,
`user_id`,
`number`
`number`,
`locked_out`
]);

const detailFields: TypedFieldset<MeetingUser> = [`about_me`, `user_id`, `meeting_id`];
Expand All @@ -57,7 +58,8 @@ export class MeetingUserRepositoryService extends BaseMeetingRelatedRepository<V
vote_delegated_to_id: partialUser.vote_delegated_to_id,
vote_delegations_from_ids: partialUser.vote_delegations_from_ids,
structure_level_ids: partialUser.structure_level_ids,
group_ids: partialUser.group_ids
group_ids: partialUser.group_ids,
locked_out: partialUser.locked_out
};

if (Object.values(partialPayload).filter(val => val !== undefined).length > 1 && partialPayload.meeting_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export type RawUser = FullNameInformation & Identifiable & Displayable & { fqid:
export type GeneralUser = ViewUser & ViewMeetingUser;

/**
* Unified type name for state fields like `is_active`, `is_physical_person` and `is_present_in_meetings`.
* Unified type name for state fields like `is_active`, `is_physical_person`, `is_present_in_meetings`
* and 'locked_out'.
*/
export type UserStateField = 'is_active' | 'is_present_in_meetings' | 'is_physical_person';
export type UserStateField = 'is_active' | 'is_present_in_meetings' | 'is_physical_person' | 'locked_out';

export interface AssignMeetingsPayload {
meeting_ids: Id[];
Expand Down Expand Up @@ -130,7 +131,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {

const participantListFields: TypedFieldset<User> = participantListFieldsMinimal
.concat(filterableListFields)
.concat([`is_present_in_meeting_ids`, `default_password`]);
.concat([`is_present_in_meeting_ids`, `default_password`, `committee_ids`, `committee_management_ids`]);

const detailFields: TypedFieldset<User> = [`default_password`, `can_change_own_password`];

Expand Down Expand Up @@ -543,7 +544,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
`comment`,
`about_me`,
`number`,
`structure_level`
`structure_level`,
`locked_out`
];
if (!create) {
fields.push(`member_number`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<os-sidenav #sideNav>
<os-sidenav #sideNav [logoLink]="showMeetingNav ? ['/'] : ['/' + meeting.id]">
<ng-template osSidenavDrawerContent>
<!-- navigation -->
<div class="main-nav">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { UserExport } from 'src/app/domain/models/users/user.export';
import { CsvColumnDefinitionProperty, CsvColumnsDefinition } from 'src/app/gateways/export/csv-export.service';
import {
CsvColumnDefinitionMap,
CsvColumnDefinitionProperty,
CsvColumnsDefinition
} from 'src/app/gateways/export/csv-export.service';
import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user';

import { MeetingCsvExportForBackendService } from '../../../../services/export/meeting-csv-export-for-backend.service';
Expand All @@ -13,6 +17,7 @@ export interface ParticipantExport extends UserExport {
comment?: string;
is_present_in_meeting_ids?: string | boolean;
group_ids?: string;
locked_out?: boolean;
}

@Injectable({
Expand Down Expand Up @@ -49,6 +54,12 @@ export class ParticipantCsvExportService {
this.csvExport.export(
participants,
participantColumns.map(key => {
if (key === `locked_out`) {
return {
label: `locked_out`,
map: user => (user.is_locked_out ? `1` : ``)
} as CsvColumnDefinitionMap<ViewUser>;
}
return {
property: key
} as CsvColumnDefinitionProperty<ViewUser>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const participantsExportExample: any = [
default_structure_level: `Berlin`,
default_vote_weight: 1.0,
group_ids: `Delegates, Staff`,
member_number: `123`
member_number: `123`,
locked_out: false
},
{
first_name: `John`,
Expand All @@ -37,7 +38,8 @@ export const participantsExportExample: any = [
username: `jbloggs`,
gender: `female`,
default_vote_weight: 1.5,
member_number: `234`
member_number: `234`,
locked_out: true
},
{
last_name: `Executive Board`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export class ViewGroup extends BaseHasMeetingUsersViewModel<Group> {
}

public hasPermission(perm: Permission): boolean {
return this.permissions?.some(
permission => permission === perm || permissionChildren[permission]?.includes(perm)
return (
this.isAdminGroup ||
this.permissions?.some(permission => permission === perm || permissionChildren[permission]?.includes(perm))
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[editMode]="isEditingSubject.value"
[goBack]="!isAllowed('seeOtherUsers')"
[hasMainButton]="isAllowed('changePersonal') && !!user"
[isSaveButtonEnabled]="isFormValid"
[isSaveButtonEnabled]="saveButtonEnabled"
[mainActionTooltip]="'Edit' | translate"
[nav]="false"
[saveAction]="getSaveAction()"
Expand Down Expand Up @@ -80,7 +80,7 @@ <h2>
[patchFormValueFn]="patchFormValueFn"
[shouldEnableFormControlFn]="shouldEnableFormControlFn"
[user]="user"
(changeEvent)="personalInfoFormValue = $event"
(changeEvent)="updateByValueChange($event)"
(errorEvent)="formErrors = $event"
(submitEvent)="saveUser()"
(validEvent)="isFormValid = $event"
Expand All @@ -98,6 +98,22 @@ <h2>{{ 'Meeting specific information' | translate }}</h2>
<span>{{ 'present' | translate }}</span>
</mat-checkbox>
</div>
<div [formGroup]="form">
<!-- Locked out? -->
<mat-checkbox
formControlName="locked_out"
matTooltipPosition="right"
matTooltip="{{ 'Lock out user from this meeting.' | translate }}"
>
<span>{{ 'locked out' | translate }}</span>
</mat-checkbox>
</div>
@if (isLockedOutAndCanManage) {
<p class="red-warning-text padding-left-12" translate>
It is not allowed to set the permisson UserCanManage to a locked out user. Please unset the
lockout before adding a group with UserCanManage.
</p>
}
<div>
<!-- Strucuture Level -->
<mat-form-field
Expand Down Expand Up @@ -299,10 +315,21 @@ <h4>{{ 'Comment' | translate }}</h4>
}
@if (isAllowed('seeName')) {
<div class="flex-vertical-center margin-top-12">
<span>{{ (user?.isPresentInMeeting() ? 'Is present' : 'Is not present') | translate }}</span>
<mat-icon class="margin-4">
{{ user!.isPresentInMeeting() ? 'check_box' : 'check_box_outline_blank' }}
</mat-icon>
<span>{{ (user?.isPresentInMeeting() ? 'Is present' : 'Is not present') | translate }}</span>
</div>
<div class="flex-vertical-center margin-top-12">
<!-- Locked out? -->
@if (isAllowed('seeSensitiveData')) {
<mat-icon class="margin-4" [class.red-warning-text]="user!.isLockedOutOfMeeting()">
{{ user!.isLockedOutOfMeeting() ? 'visibility_off' : 'check_box_outline_blank' }}
</mat-icon>
<span>
{{ (user?.isLockedOutOfMeeting() ? 'Is locked out' : 'Is not locked out') | translate }}
</span>
}
</div>
}
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ os-user-detail-view h2 {
:host ::ng-deep .detail-view {
@include detail-view-appearance;
}

.padding-left-12 {
padding-left: 12px;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild } from '@angular/core';
import { Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { marker as _ } from '@colsen1991/ngx-translate-extract-marker';
Expand All @@ -7,6 +7,7 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { Id } from 'src/app/domain/definitions/key-types';
import { OML } from 'src/app/domain/definitions/organization-permission';
import { Permission } from 'src/app/domain/definitions/permission';
import { UserDetailViewComponent } from 'src/app/site/modules/user-components';
import { BaseMeetingComponent } from 'src/app/site/pages/meetings/base/base-meeting.component';
import { ViewGroup } from 'src/app/site/pages/meetings/pages/participants';
import {
Expand Down Expand Up @@ -35,6 +36,9 @@ import { ViewStructureLevel } from '../../../structure-levels/view-models';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParticipantDetailViewComponent extends BaseMeetingComponent {
@ViewChild(UserDetailViewComponent)
private userDetailView;

public participantSubscriptionConfig = getParticipantMinimalSubscriptionConfig(this.activeMeetingId);

public readonly additionalFormControls = {
Expand All @@ -46,7 +50,8 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
group_ids: [``],
vote_delegations_from_ids: [``],
vote_delegated_to_id: [``],
is_present: [``]
is_present: [``],
locked_out: [``]
};

public sortFn = (groupA: ViewGroup, groupB: ViewGroup): number => groupA.weight - groupB.weight;
Expand All @@ -64,6 +69,9 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
if (controlName === `is_present` && user) {
return user.isPresentInMeeting();
}
if (controlName === `locked_out` && user) {
return user.isLockedOutOfMeeting();
}
const value = user?.[controlName as keyof ViewUser] || null;
return typeof value === `function` ? value.bind(user)(this.activeMeetingId!) : value;
};
Expand Down Expand Up @@ -157,6 +165,22 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
return this._isVoteDelegationEnabled;
}

public get saveButtonEnabled(): boolean {
return this.isFormValid && !this.isLockedOutAndCanManage;
}

public get isLockedOutAndCanManage(): boolean {
const lockedOutHelper = this.personalInfoFormValue?.locked_out ?? this.user?.is_locked_out;
return lockedOutHelper && this.checkSelectedGroupsCanManage();
}

public get lockoutCheckboxDisabled(): boolean {
const other = this.user?.id !== this.operator.operatorId;
const notChanged = (this.personalInfoFormValue?.locked_out ?? null) === null;
const isLockedOut = this.user?.is_locked_out;
return notChanged && !isLockedOut && (this.checkSelectedGroupsCanManage() || !other);
}

private _userId: Id | undefined = undefined; // Not initialized
private _isVoteWeightEnabled = false;
private _isVoteDelegationEnabled = false;
Expand Down Expand Up @@ -338,6 +362,17 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
}
}

public updateByValueChange(event: any): void {
this.personalInfoFormValue = event;
if (this.userDetailView.personalInfoForm.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) {
if (this.lockoutCheckboxDisabled) {
this.userDetailView.personalInfoForm.get(`locked_out`).disable();
} else {
this.userDetailView.personalInfoForm.get(`locked_out`).enable();
}
}
}

private async createUser(): Promise<void> {
const partialUser = { ...this.personalInfoFormValue };

Expand Down Expand Up @@ -424,4 +459,8 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
private goToAllUsers(): void {
this.router.navigate([this.activeMeetingId, `participants`]);
}

private checkSelectedGroupsCanManage(): boolean {
return this.usersGroups.some(group => group.hasPermission(Permission.userCanManage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ <h3>{{ 'Suitable accounts found' | translate }}</h3>
[patchFormValueFn]="patchFormValueFn"
[shouldEnableFormControlFn]="shouldEnableFormControlFn"
[user]="user"
(changeEvent)="personalInfoFormValue = $event"
(changeEvent)="updateByValueChange($event)"
(errorEvent)="formErrors = $event"
(validEvent)="isFormValid = $event"
>
Expand All @@ -153,7 +153,21 @@ <h2>{{ 'Meeting specific information' | translate }}</h2>
>
<span>{{ 'present' | translate }}</span>
</mat-checkbox>
<!-- Locked out? -->
<mat-checkbox
formControlName="locked_out"
matTooltipPosition="right"
matTooltip="{{ 'Lock out user from this meeting.' | translate }}"
>
<span>{{ 'locked out' | translate }}</span>
</mat-checkbox>
</div>
@if (isLockedOutAndCanManage) {
<p class="red-warning-text padding-left-12" translate>
It is not allowed to set the permisson UserCanManage to a locked out user.
Please unset the lockout before adding a group with UserCanManage.
</p>
}
<div>
<!-- Strucuture Level -->
<mat-form-field class="form100 force-min-width">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MatStepper } from '@angular/material/stepper';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs';
import { Id } from 'src/app/domain/definitions/key-types';
import { Permission } from 'src/app/domain/definitions/permission';
import { User } from 'src/app/domain/models/users/user';
import { SearchUsersPresenterService } from 'src/app/gateways/presenter/search-users-presenter.service';
import { createEmailValidator } from 'src/app/infrastructure/utils/validators/email';
Expand Down Expand Up @@ -51,7 +52,8 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple
group_ids: [``],
vote_delegations_from_ids: [``],
vote_delegated_to_id: [``],
is_present: [``]
is_present: [``],
locked_out: [``]
};

public get randomPasswordFn(): (() => string) | null {
Expand Down Expand Up @@ -319,4 +321,32 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple
}
}
}

public get isLockedOutAndCanManage(): boolean {
const lockedOutHelper = this.personalInfoFormValue?.locked_out ?? this.user?.is_locked_out;
return lockedOutHelper && this.checkSelectedGroupsCanManage();
}

public get lockoutCheckboxDisabled(): boolean {
const notChanged = (this.personalInfoFormValue?.locked_out ?? null) === null;
const isLockedOut = this.user?.is_locked_out;
return notChanged && !isLockedOut && this.checkSelectedGroupsCanManage();
}

public updateByValueChange(event: any): void {
this.personalInfoFormValue = event;
if (this.detailView.personalInfoForm.get(`locked_out`).disabled !== this.lockoutCheckboxDisabled) {
if (this.lockoutCheckboxDisabled) {
this.detailView.personalInfoForm.get(`locked_out`).disable();
} else {
this.detailView.personalInfoForm.get(`locked_out`).enable();
}
}
}

private checkSelectedGroupsCanManage(): boolean {
return (this.detailView.personalInfoForm.get(`group_ids`).value ?? [])
.map((id: Id): ViewGroup => this.groupRepo.getViewModel(id))
.some(group => group.hasPermission(Permission.userCanManage));
}
}
Loading