mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
74179/PR#925: Groups with linked dso (workflow groups) => No edit/delete possible + alert on edit page with link to edit roles on comcol page
This commit is contained in:
@@ -13,12 +13,12 @@ import { GROUP_EDIT_PATH } from './admin-access-control-routing-paths';
|
||||
{
|
||||
path: `${GROUP_EDIT_PATH}/:groupId`,
|
||||
component: GroupFormComponent,
|
||||
data: {title: 'admin.registries.schema.title'}
|
||||
data: {title: 'admin.access-control.groups.title.singleGroup'}
|
||||
},
|
||||
{
|
||||
path: `${GROUP_EDIT_PATH}/newGroup`,
|
||||
component: GroupFormComponent,
|
||||
data: {title: 'admin.registries.schema.title'}
|
||||
data: {title: 'admin.access-control.groups.title.addGroup'}
|
||||
},
|
||||
])
|
||||
]
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<div class="container">
|
||||
<div class="group-form row">
|
||||
<div class="col-12">
|
||||
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
|
||||
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
||||
<ds-alert *ngIf="!(canEdit$ | async)" [type]="AlertTypeEnum.Warning"
|
||||
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: (getLinkedDSO(groupBeingEdited) | async)?.payload?.name, comcol: (getLinkedDSO(groupBeingEdited) | async)?.payload?.type, comcolEditRolesRoute: (getLinkedEditRolesRoute(groupBeingEdited) | async) })">
|
||||
</ds-alert>
|
||||
|
||||
<div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div>
|
||||
|
||||
@@ -19,14 +24,17 @@
|
||||
(cancel)="onCancel()"
|
||||
(submitForm)="onSubmit()">
|
||||
<div *ngIf="groupBeingEdited != null" class="row">
|
||||
<button class="btn btn-light delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()">
|
||||
<button class="btn btn-light delete-button" [disabled]="!(canEdit$ | async) || groupBeingEdited.permanent"
|
||||
(click)="delete()">
|
||||
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</ds-form>
|
||||
|
||||
<ds-members-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||
<ds-subgroups-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||
<ds-members-list *ngIf="groupBeingEdited != null"
|
||||
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||
<ds-subgroups-list *ngIf="groupBeingEdited != null"
|
||||
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||
|
||||
<div>
|
||||
<button [routerLink]="[this.groupDataService.getGroupRegistryRouterLink()]"
|
||||
|
@@ -12,20 +12,30 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { switchMap, take } from 'rxjs/operators';
|
||||
import { ObservedValueOf } from 'rxjs/internal/types';
|
||||
import { map, switchMap, take } from 'rxjs/operators';
|
||||
import { getCollectionEditRolesRoute } from '../../../../+collection-page/collection-page-routing-paths';
|
||||
import { getCommunityEditRolesRoute } from '../../../../+community-page/community-page-routing-paths';
|
||||
import { RestResponse } from '../../../../core/cache/response.models';
|
||||
import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
|
||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { RequestService } from '../../../../core/data/request.service';
|
||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||
import { Group } from '../../../../core/eperson/models/group.model';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { AlertType } from '../../../../shared/alert/aletr-type';
|
||||
import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component';
|
||||
import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-group-form',
|
||||
@@ -98,10 +108,17 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Observable whether or not the logged in user is allowed to delete the Group
|
||||
*/
|
||||
canDelete$: Observable<boolean>;
|
||||
canEdit$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The AlertType enumeration
|
||||
* @type {AlertType}
|
||||
*/
|
||||
public AlertTypeEnum = AlertType;
|
||||
|
||||
constructor(public groupDataService: GroupDataService,
|
||||
private ePersonDataService: EPersonDataService,
|
||||
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private translateService: TranslateService,
|
||||
private notificationsService: NotificationsService,
|
||||
@@ -120,12 +137,19 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.subs.push(this.route.params.subscribe((params) => {
|
||||
this.setActiveGroup(params.groupId)
|
||||
}));
|
||||
this.canDelete$ = this.groupDataService.getActiveGroup().pipe(
|
||||
switchMap((group: Group) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined))
|
||||
this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
|
||||
switchMap((group: Group) => {
|
||||
return combineLatest(
|
||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
|
||||
this.hasLinkedDSO(group),
|
||||
(isAuthorized: ObservedValueOf<Observable<boolean>>, hasLinkedDSO: ObservedValueOf<Observable<boolean>>) => {
|
||||
return isAuthorized && !hasLinkedDSO;
|
||||
})
|
||||
})
|
||||
);
|
||||
combineLatest(
|
||||
this.translateService.get(`${this.messagePrefix}.groupName`),
|
||||
this.translateService.get(`${this.messagePrefix}.groupDescription`),
|
||||
this.translateService.get(`${this.messagePrefix}.groupDescription`)
|
||||
).subscribe(([groupName, groupDescription]) => {
|
||||
this.groupName = new DynamicInputModel({
|
||||
id: 'groupName',
|
||||
@@ -147,18 +171,23 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.groupDescription,
|
||||
];
|
||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||
this.subs.push(
|
||||
combineLatest(
|
||||
this.groupDataService.getActiveGroup(),
|
||||
this.canEdit$
|
||||
).subscribe(([activeGroup, canEdit]) => {
|
||||
if (activeGroup != null) {
|
||||
this.groupBeingEdited = activeGroup;
|
||||
this.formGroup.patchValue({
|
||||
groupName: activeGroup != null ? activeGroup.name : '',
|
||||
groupDescription: activeGroup != null ? activeGroup.firstMetadataValue('dc.description') : '',
|
||||
});
|
||||
if (activeGroup.permanent) {
|
||||
this.formGroup.get('groupName').disable();
|
||||
if (!canEdit || activeGroup.permanent) {
|
||||
this.formGroup.disable();
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -298,7 +327,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||
if (activeGroup === null) {
|
||||
this.groupDataService.cancelEditGroup();
|
||||
this.groupDataService.findByHref(groupSelfLink)
|
||||
this.groupDataService.findByHref(groupSelfLink, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload())
|
||||
@@ -335,7 +364,8 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content', { cause: optionalErrorMessage }));
|
||||
}
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -358,4 +388,57 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.onCancel();
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if group has a linked object (community or collection linked to a workflow group)
|
||||
* @param group
|
||||
*/
|
||||
hasLinkedDSO(group: Group): Observable<boolean> {
|
||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
||||
return this.getLinkedDSO(group).pipe(
|
||||
map((rd: RemoteData<DSpaceObject>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group's linked object if it has one (community or collection linked to a workflow group)
|
||||
* @param group
|
||||
*/
|
||||
getLinkedDSO(group: Group): Observable<RemoteData<DSpaceObject>> {
|
||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
||||
if (group.object == undefined) {
|
||||
return this.dSpaceObjectDataService.findByHref(group._links.object.href);
|
||||
}
|
||||
return group.object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route to the edit roles tab of the group's linked object (community or collection linked to a workflow group) if it has one
|
||||
* @param group
|
||||
*/
|
||||
getLinkedEditRolesRoute(group: Group): Observable<String> {
|
||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
||||
return this.getLinkedDSO(group).pipe(
|
||||
map((rd: RemoteData<DSpaceObject>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
const dso = rd.payload
|
||||
switch ((dso as any).type) {
|
||||
case Community.type.value:
|
||||
return getCommunityEditRolesRoute(rd.payload.id);
|
||||
case Collection.type.value:
|
||||
return getCollectionEditRolesRoute(rd.payload.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { filter } from 'rxjs/internal/operators/filter';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { ObservedValueOf } from 'rxjs/internal/types';
|
||||
import { map, switchMap, take } from 'rxjs/operators';
|
||||
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
@@ -17,6 +19,7 @@ import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||
import { GroupDtoModel } from '../../../core/eperson/models/group-dto.model';
|
||||
import { Group } from '../../../core/eperson/models/group.model';
|
||||
import { RouteService } from '../../../core/services/route.service';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
@@ -72,6 +75,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(public groupService: GroupDataService,
|
||||
private ePersonDataService: EPersonDataService,
|
||||
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||
private translateService: TranslateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private formBuilder: FormBuilder,
|
||||
@@ -120,16 +124,18 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.subs.push(this.groups$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
switchMap((groups) => {
|
||||
return combineLatest(...groups.page.map((group) => {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined).pipe(
|
||||
map((authorized: boolean) => {
|
||||
switchMap((groups: PaginatedList<Group>) => {
|
||||
return combineLatest(...groups.page.map((group: Group) => {
|
||||
return combineLatest(
|
||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
|
||||
this.hasLinkedDSO(group),
|
||||
(isAuthorized: ObservedValueOf<Observable<boolean>>, hasLinkedDSO: ObservedValueOf<Observable<boolean>>) => {
|
||||
const groupDtoModel: GroupDtoModel = new GroupDtoModel();
|
||||
groupDtoModel.ableToDelete = authorized;
|
||||
groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO;
|
||||
groupDtoModel.group = group;
|
||||
return groupDtoModel;
|
||||
})
|
||||
);
|
||||
}
|
||||
)
|
||||
})).pipe(map((dtos: GroupDtoModel[]) => {
|
||||
return new PaginatedList(groups.pageInfo, dtos);
|
||||
}))
|
||||
@@ -188,6 +194,25 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
return this.groupService.findAllByHref(group._links.subgroups.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if group has a linked object (community or collection linked to a workflow group)
|
||||
* @param group
|
||||
*/
|
||||
hasLinkedDSO(group: Group): Observable<boolean> {
|
||||
if (group.object == undefined) {
|
||||
group.object = this.dSpaceObjectDataService.findByHref(group._links.object.href);
|
||||
}
|
||||
return group.object.pipe(
|
||||
map((rd: RemoteData<DSpaceObject>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all input-fields to be empty and search all search
|
||||
*/
|
||||
|
@@ -20,6 +20,11 @@ export function getCollectionCreateRoute() {
|
||||
return new URLCombiner(getCollectionModuleRoute(), COLLECTION_CREATE_PATH).toString()
|
||||
}
|
||||
|
||||
export function getCollectionEditRolesRoute(id) {
|
||||
return new URLCombiner(getCollectionPageRoute(id), COLLECTION_EDIT_PATH, COLLECTION_EDIT_ROLES_PATH).toString()
|
||||
}
|
||||
|
||||
export const COLLECTION_CREATE_PATH = 'create';
|
||||
export const COLLECTION_EDIT_PATH = 'edit';
|
||||
export const COLLECTION_EDIT_ROLES_PATH = 'roles';
|
||||
export const ITEMTEMPLATE_PATH = 'itemtemplate';
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { getCollectionPageRoute } from '../+collection-page/collection-page-routing-paths';
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
|
||||
export const COMMUNITY_PARENT_PARAMETER = 'parent';
|
||||
@@ -20,5 +21,10 @@ export function getCommunityCreateRoute() {
|
||||
return new URLCombiner(getCommunityModuleRoute(), COMMUNITY_CREATE_PATH).toString()
|
||||
}
|
||||
|
||||
export function getCommunityEditRolesRoute(id) {
|
||||
return new URLCombiner(getCollectionPageRoute(id), COMMUNITY_EDIT_PATH, COMMUNITY_EDIT_ROLES_PATH).toString()
|
||||
}
|
||||
|
||||
export const COMMUNITY_CREATE_PATH = 'create';
|
||||
export const COMMUNITY_EDIT_PATH = 'edit';
|
||||
export const COMMUNITY_EDIT_ROLES_PATH = 'roles';
|
||||
|
@@ -58,4 +58,8 @@ export class DSpaceObjectDataService {
|
||||
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findById(uuid);
|
||||
}
|
||||
|
||||
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findByHref(href);
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import { PaginatedList } from '../../data/paginated-list';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
|
||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||
import { DSPACE_OBJECT } from '../../shared/dspace-object.resource-type';
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
import { EPerson } from './eperson.model';
|
||||
import { EPERSON } from './eperson.resource-type';
|
||||
@@ -41,6 +42,7 @@ export class Group extends DSpaceObject {
|
||||
self: HALLink;
|
||||
subgroups: HALLink;
|
||||
epersons: HALLink;
|
||||
object: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -57,4 +59,11 @@ export class Group extends DSpaceObject {
|
||||
@link(EPERSON, true)
|
||||
public epersons?: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||
|
||||
/**
|
||||
* Connected dspace object, the community or collection connected to a workflow group (204 no content for non-workflow groups)
|
||||
* Will be undefined unless the object {@link HALLink} has been resolved (can only be resolved for workflow groups)
|
||||
*/
|
||||
@link(DSPACE_OBJECT)
|
||||
public object?: Observable<RemoteData<DSpaceObject>>;
|
||||
|
||||
}
|
||||
|
@@ -280,6 +280,10 @@
|
||||
|
||||
"admin.access-control.groups.title": "DSpace Angular :: Groups",
|
||||
|
||||
"admin.access-control.groups.title.singleGroup": "DSpace Angular :: Edit Group",
|
||||
|
||||
"admin.access-control.groups.title.addGroup": "DSpace Angular :: New Group",
|
||||
|
||||
"admin.access-control.groups.head": "Groups",
|
||||
|
||||
"admin.access-control.groups.button.add": "Add group",
|
||||
@@ -313,6 +317,11 @@
|
||||
"admin.access-control.groups.notification.deleted.failure.content": "Cause: \"{{cause}}\"",
|
||||
|
||||
|
||||
|
||||
"admin.access-control.groups.form.alert.permanent": "This group is permanent, so it can't be edited or deleted. You can still add and remove group members using this page.",
|
||||
|
||||
"admin.access-control.groups.form.alert.workflowGroup": "This group can’t be modified or deleted because it corresponds to a role in the submission and workflow process in the \"{{name}}\" {{comcol}}. You can delete it from the <a href='{{comcolEditRolesRoute}}'>\"assign roles\"</a> tab on the edit {{comcol}} page. You can still add and remove group members using this page.",
|
||||
|
||||
"admin.access-control.groups.form.head.create": "Create group",
|
||||
|
||||
"admin.access-control.groups.form.head.edit": "Edit group",
|
||||
|
Reference in New Issue
Block a user