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

Difficult to find users to delete from group in large groups #3286

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ <h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.remove' | translate}}</th>
</tr>
</thead>
<tbody>
Expand All @@ -78,6 +79,13 @@ <h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
<td class="align-middle">
{{ dsoNameService.getName((group.object | async)?.payload) }}
</td>
<td class="align-middle">
<button *ngIf="canDelete$ | async" before (click)="deleteGroupFromMember(group)"
type="button" [ngClass]="['btn btn-sm delete-button', 'btn-outline-danger']"
title="{{messagePrefix + '.table.edit.buttons.removegroup' | translate: { name: dsoNameService.getName(group) } }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
</td>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,38 @@
}
}

/**
* Deletes a given group from the Group list of the eperson currently being edited present in
* @param group group we want to delete as of which the current eperson being edited is member of
*/
deleteGroupFromMember(group: Group) {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {

Check warning on line 605 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L605

Added line #L605 was not covered by tests
if (activeEPerson != null) {
const response = this.groupsDataService.deleteMemberFromGroup(group, activeEPerson);
this.showNotifications('deleteMembership', response, this.dsoNameService.getName(group), activeEPerson);

Check warning on line 608 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L607-L608

Added lines #L607 - L608 were not covered by tests
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveEPerson'));

Check warning on line 610 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L610

Added line #L610 was not covered by tests
}
});
}

/**
* Shows a notification based on the success/failure of the request
* @param messageSuffix Suffix for message
* @param response RestResponse observable containing success/failure request
* @param nameObject Object request was about
* @param activeEPerson EPerson currently being edited
*/
showNotifications(messageSuffix: string, response: Observable<RemoteData<any>>, nameObject: string, activeEPerson: EPerson) {
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<any>) => {

Check warning on line 623 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L623

Added line #L623 was not covered by tests
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));

Check warning on line 625 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L625

Added line #L625 was not covered by tests
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));

Check warning on line 627 in src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts#L627

Added line #L627 was not covered by tests
}
});
}

/**
* Cancel the current edit when component is destroyed & unsub all subscriptions
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
<h2 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h2>

<h3>{{messagePrefix + '.headMembers' | translate}}</h3>

<form [formGroup]="searchCurrentMembersForm" (ngSubmit)="searchMembers(searchCurrentMembersForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3">
<div class="form-group input-group mr-3">
<input type="text" name="queryCurrentMembers" id="queryCurrentMembers" formControlName="queryCurrentMembers"
class="form-control" aria-label="Search input">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
</span>
</div>
</div>
<div>
<button (click)="clearCurrentMembersFormAndResetResult()"
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
</div>
</form>

<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.totalElements > 0"
[paginationOptions]="config"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,21 @@
// The search form
searchForm;

// The current member search form
searchCurrentMembersForm;

// Current search in edit group - epeople search form
currentSearchQuery: string;

// Current search in edit group - epeople current members search form
currentMembersSearchQuery: string;

// Whether or not user has done a EPeople search yet
searchDone: boolean;

// Whether or not user has done a EPeople Member search yet
searchCurrentMembersDone: boolean;

// current active group being edited
groupBeingEdited: Group;

Expand All @@ -194,12 +203,18 @@
public dsoNameService: DSONameService,
) {
this.currentSearchQuery = '';
this.currentMembersSearchQuery = '';
}

ngOnInit(): void {
this.searchForm = this.formBuilder.group(({
query: '',
}));

this.searchCurrentMembersForm = this.formBuilder.group(({
queryCurrentMembers: '',
}));

this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
if (activeGroup != null) {
this.groupBeingEdited = activeGroup;
Expand Down Expand Up @@ -354,6 +369,55 @@
}));
}

/**
* Search all EPeople who are a member of the current group by name, email or metadata
* @param data Contains query param
*/
searchMembers(data: any) {
this.unsubFrom(SubKey.Members);
this.subs.set(SubKey.Members,

Check warning on line 378 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L377-L378

Added lines #L377 - L378 were not covered by tests
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
switchMap((paginationOptions) => {
const query: string = data.queryCurrentMembers;

Check warning on line 381 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L381

Added line #L381 was not covered by tests
if (query != null && this.currentMembersSearchQuery !== query && this.groupBeingEdited) {
this.currentMembersSearchQuery = query;
this.paginationService.resetPage(this.config.id);

Check warning on line 384 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L383-L384

Added lines #L383 - L384 were not covered by tests
}
this.searchCurrentMembersDone = true;

Check warning on line 386 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L386

Added line #L386 was not covered by tests

return this.ePersonDataService.searchMembers(this.currentMembersSearchQuery, this.groupBeingEdited.id, {

Check warning on line 388 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L388

Added line #L388 was not covered by tests
currentPage: paginationOptions.currentPage,
elementsPerPage: paginationOptions.pageSize,
}, false, true);
}),
getAllCompletedRemoteData(),
map((rd: RemoteData<any>) => {
if (rd.hasFailed) {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage }));

Check warning on line 396 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L396

Added line #L396 was not covered by tests
} else {
return rd;

Check warning on line 398 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L398

Added line #L398 was not covered by tests
}
}),
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(

Check warning on line 403 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L402-L403

Added lines #L402 - L403 were not covered by tests
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
epersonDtoModel.eperson = member;
epersonDtoModel.ableToDelete = isMember;
return epersonDtoModel;

Check warning on line 408 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L405-L408

Added lines #L405 - L408 were not covered by tests
});
return dto$;

Check warning on line 410 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L410

Added line #L410 was not covered by tests
})]);
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);

Check warning on line 413 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L412-L413

Added lines #L412 - L413 were not covered by tests
}));
}),
).subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
this.ePeopleMembersOfGroup.next(paginatedListOfDTOs);

Check warning on line 417 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L417

Added line #L417 was not covered by tests
}));
}

/**
* unsub all subscriptions
*/
Expand Down Expand Up @@ -391,4 +455,14 @@
});
this.search({ query: '' });
}

/**
* Reset all input-fields to be empty and search all search
*/
clearCurrentMembersFormAndResetResult() {
this.searchCurrentMembersForm.patchValue({

Check warning on line 463 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L463

Added line #L463 was not covered by tests
queryCurrentMembers:'',
});
this.searchMembers({ queryCurrentMembers: '' });

Check warning on line 466 in src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts#L466

Added line #L466 was not covered by tests
}
}
28 changes: 28 additions & 0 deletions src/app/core/eperson/eperson-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,34 @@
return this.searchBy('isNotMemberOf', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}

/**
* Searches for all EPerons which are a member of a given group, via a passed in query
* (searches all EPerson metadata and by exact UUID).
* Endpoint used: /eperson/epesons/search/isMemberOf?query=<:string>&group=<:uuid>
* @param query search query param
* @param group UUID of group to include results from. Members of this group will only be returned.
* @param options
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
public searchMembers(query: string, group: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
const searchParams = [new RequestParam('query', query), new RequestParam('group', group)];
let findListOptions = new FindListOptions();

Check warning on line 260 in src/app/core/eperson/eperson-data.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/eperson/eperson-data.service.ts#L259-L260

Added lines #L259 - L260 were not covered by tests
if (options) {
findListOptions = Object.assign(new FindListOptions(), options);

Check warning on line 262 in src/app/core/eperson/eperson-data.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/eperson/eperson-data.service.ts#L262

Added line #L262 was not covered by tests
}
if (findListOptions.searchParams) {
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];

Check warning on line 265 in src/app/core/eperson/eperson-data.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/eperson/eperson-data.service.ts#L265

Added line #L265 was not covered by tests
} else {
findListOptions.searchParams = searchParams;

Check warning on line 267 in src/app/core/eperson/eperson-data.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/eperson/eperson-data.service.ts#L267

Added line #L267 was not covered by tests
}
return this.searchBy('isMemberOf', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);

Check warning on line 269 in src/app/core/eperson/eperson-data.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/core/eperson/eperson-data.service.ts#L269

Added line #L269 was not covered by tests
}

/**
* Add a new patch to the object cache
* The patch is derived from the differences between the given object and its version in the object cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import {
ReactiveFormsModule,
UntypedFormBuilder,
UntypedFormGroup,
} from '@angular/forms';
import {
Router,
Expand Down Expand Up @@ -94,6 +95,8 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn

selectedReviewers: EPerson[] = [];

searchCurrentMembersForm: UntypedFormGroup;

constructor(
protected groupService: GroupDataService,
public ePersonDataService: EPersonDataService,
Expand All @@ -112,6 +115,10 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
scope: 'metadata',
query: '',
}));

this.searchCurrentMembersForm = this.formBuilder.group(({
queryCurrentMembers: '',
}));
}

ngOnChanges(changes: SimpleChanges): void {
Expand Down
4 changes: 4 additions & 0 deletions src/assets/i18n/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@

"admin.access-control.epeople.form.table.collectionOrCommunity": "Collection/Community",

"admin.access-control.epeople.form.table.remove": "Remove",

"admin.access-control.epeople.form.table.edit.buttons.removegroup": "Remove member from group \"{{name}}\"",

"admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups",

"admin.access-control.epeople.form.goToGroups": "Add to groups",
Expand Down
Loading