mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 22:43:03 +00:00
Merge branch 'main' into #885-media-viewer
# Conflicts: # src/app/+item-page/item-page.module.ts
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
|
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
|
||||||
- docker-compose-rest.yml
|
- docker-compose-rest.yml
|
||||||
- Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes
|
- Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes
|
||||||
- docker-compose-travis.yml
|
- docker-compose-ci.yml
|
||||||
- Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup.
|
- Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup.
|
||||||
- cli.yml
|
- cli.yml
|
||||||
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
||||||
|
@@ -1,3 +1,17 @@
|
|||||||
|
#
|
||||||
|
# The contents of this file are subject to the license and copyright
|
||||||
|
# detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
# tree and available online at
|
||||||
|
#
|
||||||
|
# http://www.dspace.org/license/
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a copy of the cli.ingest.yml that is available in the DSpace/DSpace
|
||||||
|
# (Backend) at:
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml
|
||||||
|
#
|
||||||
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
@@ -8,7 +22,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
dspacenet: {}
|
dspacenet: {}
|
||||||
environment:
|
environment:
|
||||||
- LOADASSETS=https://www.dropbox.com/s/zv7lj8j2lp3egjs/assetstore.tar.gz?dl=1
|
- LOADASSETS=https://www.dropbox.com/s/v3ahfcuatklbmi0/assetstore-2019-11-28.tar.gz?dl=1
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
@@ -21,3 +35,5 @@ services:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
/dspace/bin/dspace index-discovery
|
/dspace/bin/dspace index-discovery
|
||||||
|
/dspace/bin/dspace oai import
|
||||||
|
/dspace/bin/dspace oai clean-cache
|
||||||
|
@@ -6,6 +6,12 @@
|
|||||||
# http://www.dspace.org/license/
|
# http://www.dspace.org/license/
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a copy of the cli.ingest.yml that is available in the DSpace/DSpace
|
||||||
|
# (Backend) at:
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml
|
||||||
|
#
|
||||||
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@@ -1,3 +1,17 @@
|
|||||||
|
#
|
||||||
|
# The contents of this file are subject to the license and copyright
|
||||||
|
# detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
# tree and available online at
|
||||||
|
#
|
||||||
|
# http://www.dspace.org/license/
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a copy of the docker-compose-cli.yml that is available in the DSpace/DSpace
|
||||||
|
# (Backend) at:
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/docker-compose-cli.yml
|
||||||
|
#
|
||||||
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@@ -6,11 +6,17 @@
|
|||||||
# http://www.dspace.org/license/
|
# http://www.dspace.org/license/
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a copy of the db.entities.yml that is available in the DSpace/DSpace
|
||||||
|
# (Backend) at:
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
||||||
|
#
|
||||||
|
# # Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspacedb:
|
dspacedb:
|
||||||
image: dspace/dspace-postgres-pgcrypto:loadsql
|
image: dspace/dspace-postgres-pgcrypto:loadsql
|
||||||
environment:
|
environment:
|
||||||
# Double underbars in env names will be replaced with periods for apache commons
|
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
|
||||||
- LOADSQL=https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
|
- LOADSQL=https://www.dropbox.com/s/4ap1y6deseoc8ws/dspace7-entities-2019-11-28.sql?dl=1
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
# Docker Compose for running the DSpace backend for e2e testing in CI
|
#
|
||||||
|
# The contents of this file are subject to the license and copyright
|
||||||
|
# detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
# tree and available online at
|
||||||
|
#
|
||||||
|
# http://www.dspace.org/license/
|
||||||
|
#
|
||||||
|
|
||||||
|
# Docker Compose for running the DSpace backend for e2e testing in a CI environment
|
||||||
|
# This is used by our GitHub CI at .github/workflows/build.yml
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
services:
|
services:
|
||||||
@@ -20,7 +29,9 @@ services:
|
|||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
environment:
|
environment:
|
||||||
LOADSQL: https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
|
# This LOADSQL should be kept in sync with the LOADSQL in
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
||||||
|
LOADSQL: https://www.dropbox.com/s/4ap1y6deseoc8ws/dspace7-entities-2019-11-28.sql?dl=1
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: dspace/dspace-postgres-pgcrypto:loadsql
|
image: dspace/dspace-postgres-pgcrypto:loadsql
|
||||||
networks:
|
networks:
|
||||||
|
@@ -1,11 +1,24 @@
|
|||||||
|
#
|
||||||
|
# The contents of this file are subject to the license and copyright
|
||||||
|
# detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
# tree and available online at
|
||||||
|
#
|
||||||
|
# http://www.dspace.org/license/
|
||||||
|
#
|
||||||
|
|
||||||
|
# Docker Compose for running the DSpace backend for testing/development
|
||||||
|
# This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace
|
||||||
|
# (Backend) at:
|
||||||
|
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
|
||||||
|
version: '3.7'
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
services:
|
services:
|
||||||
dspace:
|
dspace:
|
||||||
container_name: dspace
|
container_name: dspace
|
||||||
|
image: dspace/dspace:dspace-7_x-test
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:dspace-7_x-test
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
ports:
|
ports:
|
||||||
@@ -16,20 +29,27 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
- "./local.cfg:/dspace/config/local.cfg"
|
||||||
# Ensure that the database is ready before starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
|
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||||
|
# 2. Then, run database migration to init database tables
|
||||||
|
# 3. Finally, start Tomcat
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
|
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
||||||
/dspace/bin/dspace database migrate
|
/dspace/bin/dspace database migrate
|
||||||
catalina.sh run
|
catalina.sh run
|
||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
image: dspace/dspace-postgres-pgcrypto
|
|
||||||
environment:
|
environment:
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
|
image: dspace/dspace-postgres-pgcrypto
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
|
ports:
|
||||||
|
- published: 5432
|
||||||
|
target: 5432
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
@@ -49,7 +69,6 @@ services:
|
|||||||
- solr_oai:/opt/solr/server/solr/oai/data
|
- solr_oai:/opt/solr/server/solr/oai/data
|
||||||
- solr_search:/opt/solr/server/solr/search/data
|
- solr_search:/opt/solr/server/solr/search/data
|
||||||
- solr_statistics:/opt/solr/server/solr/statistics/data
|
- solr_statistics:/opt/solr/server/solr/statistics/data
|
||||||
version: '3.7'
|
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
@@ -1,3 +1,14 @@
|
|||||||
|
#
|
||||||
|
# The contents of this file are subject to the license and copyright
|
||||||
|
# detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
|
# tree and available online at
|
||||||
|
#
|
||||||
|
# http://www.dspace.org/license/
|
||||||
|
#
|
||||||
|
|
||||||
|
# Docker Compose for running the DSpace Angular UI for testing/development
|
||||||
|
# Requires also running a REST API backend (either locally or remotely),
|
||||||
|
# for example via 'docker-compose-rest.yml'
|
||||||
version: '3.7'
|
version: '3.7'
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
|
@@ -13,12 +13,12 @@ import { GROUP_EDIT_PATH } from './admin-access-control-routing-paths';
|
|||||||
{
|
{
|
||||||
path: `${GROUP_EDIT_PATH}/:groupId`,
|
path: `${GROUP_EDIT_PATH}/:groupId`,
|
||||||
component: GroupFormComponent,
|
component: GroupFormComponent,
|
||||||
data: {title: 'admin.registries.schema.title'}
|
data: {title: 'admin.access-control.groups.title.singleGroup'}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${GROUP_EDIT_PATH}/newGroup`,
|
path: `${GROUP_EDIT_PATH}/newGroup`,
|
||||||
component: GroupFormComponent,
|
component: GroupFormComponent,
|
||||||
data: {title: 'admin.registries.schema.title'}
|
data: {title: 'admin.access-control.groups.title.addGroup'}
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(ePeopleDto$ | async)?.totalElements > 0"
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
[pageInfoState]="pageInfoState$"
|
[pageInfoState]="pageInfoState$"
|
||||||
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||||
|
@@ -44,7 +44,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> {
|
getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
|
||||||
},
|
},
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return observableOf(this.activeEPerson);
|
return observableOf(this.activeEPerson);
|
||||||
@@ -54,18 +54,18 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
const result = this.allEpeople.find((ePerson: EPerson) => {
|
const result = this.allEpeople.find((ePerson: EPerson) => {
|
||||||
return ePerson.email === query
|
return ePerson.email === query
|
||||||
});
|
});
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
|
||||||
}
|
}
|
||||||
if (scope === 'metadata') {
|
if (scope === 'metadata') {
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
|
||||||
}
|
}
|
||||||
const result = this.allEpeople.find((ePerson: EPerson) => {
|
const result = this.allEpeople.find((ePerson: EPerson) => {
|
||||||
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
||||||
});
|
});
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
|
||||||
}
|
}
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
|
||||||
},
|
},
|
||||||
deleteEPerson(ePerson: EPerson): Observable<boolean> {
|
deleteEPerson(ePerson: EPerson): Observable<boolean> {
|
||||||
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
||||||
|
@@ -141,7 +141,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
|||||||
currentPage: this.config.currentPage,
|
currentPage: this.config.currentPage,
|
||||||
elementsPerPage: this.config.pageSize
|
elementsPerPage: this.config.pageSize
|
||||||
}).subscribe((peopleRD) => {
|
}).subscribe((peopleRD) => {
|
||||||
this.ePeople$.next(peopleRD)
|
this.ePeople$.next(peopleRD);
|
||||||
|
this.pageInfoState$.next(peopleRD.payload.pageInfo);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="group-form row">
|
<div class="group-form row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
|
||||||
|
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
||||||
|
<ds-alert *ngIf="!(canEdit$ | async) && (groupDataService.getActiveGroup() | 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>
|
<div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
@@ -18,10 +23,18 @@
|
|||||||
[formLayout]="formLayout"
|
[formLayout]="formLayout"
|
||||||
(cancel)="onCancel()"
|
(cancel)="onCancel()"
|
||||||
(submitForm)="onSubmit()">
|
(submitForm)="onSubmit()">
|
||||||
|
<div *ngIf="groupBeingEdited != null" class="row">
|
||||||
|
<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-form>
|
||||||
|
|
||||||
<ds-members-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
<ds-members-list *ngIf="groupBeingEdited != null"
|
||||||
<ds-subgroups-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||||
|
<ds-subgroups-list *ngIf="groupBeingEdited != null"
|
||||||
|
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button [routerLink]="[this.groupDataService.getGroupRegistryRouterLink()]"
|
<button [routerLink]="[this.groupDataService.getGroupRegistryRouterLink()]"
|
||||||
|
@@ -14,11 +14,14 @@ import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-d
|
|||||||
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
import { RestResponse } from '../../../../core/cache/response.models';
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
||||||
|
import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
|
||||||
|
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { UUIDService } from '../../../../core/shared/uuid.service';
|
import { UUIDService } from '../../../../core/shared/uuid.service';
|
||||||
@@ -40,6 +43,9 @@ describe('GroupFormComponent', () => {
|
|||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
|
let dsoDataServiceStub: any;
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
let notificationService: NotificationsServiceStub;
|
||||||
let router;
|
let router;
|
||||||
|
|
||||||
let groups;
|
let groups;
|
||||||
@@ -74,6 +80,9 @@ describe('GroupFormComponent', () => {
|
|||||||
editGroup(group: Group) {
|
editGroup(group: Group) {
|
||||||
this.activeGroup = group
|
this.activeGroup = group
|
||||||
},
|
},
|
||||||
|
updateGroup(group: Group) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
cancelEditGroup(): void {
|
cancelEditGroup(): void {
|
||||||
this.activeGroup = null;
|
this.activeGroup = null;
|
||||||
},
|
},
|
||||||
@@ -88,9 +97,18 @@ describe('GroupFormComponent', () => {
|
|||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
|
isAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
|
dsoDataServiceStub = {
|
||||||
|
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
translateService = getMockTranslateService();
|
translateService = getMockTranslateService();
|
||||||
router = new RouterMock();
|
router = new RouterMock();
|
||||||
|
notificationService = new NotificationsServiceStub();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
@@ -104,7 +122,8 @@ describe('GroupFormComponent', () => {
|
|||||||
providers: [GroupFormComponent,
|
providers: [GroupFormComponent,
|
||||||
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
{ provide: DSpaceObjectDataService, useValue: dsoDataServiceStub },
|
||||||
|
{ provide: NotificationsService, useValue: notificationService },
|
||||||
{ provide: FormBuilderService, useValue: builderService },
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
{ provide: DSOChangeAnalyzer, useValue: {} },
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
{ provide: HttpClient, useValue: {} },
|
{ provide: HttpClient, useValue: {} },
|
||||||
@@ -115,6 +134,7 @@ describe('GroupFormComponent', () => {
|
|||||||
{ provide: HALEndpointService, useValue: {} },
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }), params: observableOf({}) } },
|
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }), params: observableOf({}) } },
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -148,6 +168,34 @@ describe('GroupFormComponent', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
describe('with active Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
|
||||||
|
spyOn(groupsDataServiceStub, 'updateGroup').and.returnValue(observableOf(new RestResponse(true, 200, 'OK')));
|
||||||
|
component.groupName.value = 'newGroupName';
|
||||||
|
component.onSubmit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit the existing group using the correct new values', async(() => {
|
||||||
|
const expected2 = Object.assign(new Group(), {
|
||||||
|
name: 'newGroupName',
|
||||||
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: groupDescription
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(component.submitForm.emit).toHaveBeenCalledWith(expected2);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
it('should emit success notification', () => {
|
||||||
|
expect(notificationService.success).toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {
|
import {
|
||||||
DynamicFormControlModel,
|
DynamicFormControlModel,
|
||||||
DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
@@ -8,18 +9,30 @@ import {
|
|||||||
DynamicTextAreaModel
|
DynamicTextAreaModel
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
import { ObservedValueOf, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
import { catchError, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { 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 { 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 { 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 { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
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 { 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 { hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-group-form',
|
selector: 'ds-group-form',
|
||||||
@@ -89,22 +102,51 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
groupBeingEdited: Group;
|
groupBeingEdited: Group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable whether or not the logged in user is allowed to delete the Group & doesn't have a linked object (community / collection linked to workspace group
|
||||||
|
*/
|
||||||
|
canEdit$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AlertType enumeration
|
||||||
|
* @type {AlertType}
|
||||||
|
*/
|
||||||
|
public AlertTypeEnum = AlertType;
|
||||||
|
|
||||||
constructor(public groupDataService: GroupDataService,
|
constructor(public groupDataService: GroupDataService,
|
||||||
private ePersonDataService: EPersonDataService,
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||||
private formBuilderService: FormBuilderService,
|
private formBuilderService: FormBuilderService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
|
private authorizationService: AuthorizationDataService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
public requestService: RequestService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.initialisePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialisePage() {
|
||||||
this.subs.push(this.route.params.subscribe((params) => {
|
this.subs.push(this.route.params.subscribe((params) => {
|
||||||
this.setActiveGroup(params.groupId)
|
this.setActiveGroup(params.groupId)
|
||||||
}));
|
}));
|
||||||
combineLatest(
|
this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
|
||||||
|
switchMap((group: Group) => {
|
||||||
|
return observableCombineLatest(
|
||||||
|
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
|
||||||
|
this.hasLinkedDSO(group),
|
||||||
|
(isAuthorized: ObservedValueOf<Observable<boolean>>, hasLinkedDSO: ObservedValueOf<Observable<boolean>>) => {
|
||||||
|
return isAuthorized && !hasLinkedDSO;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
observableCombineLatest(
|
||||||
this.translateService.get(`${this.messagePrefix}.groupName`),
|
this.translateService.get(`${this.messagePrefix}.groupName`),
|
||||||
this.translateService.get(`${this.messagePrefix}.groupDescription`),
|
this.translateService.get(`${this.messagePrefix}.groupDescription`)
|
||||||
).subscribe(([groupName, groupDescription]) => {
|
).subscribe(([groupName, groupDescription]) => {
|
||||||
this.groupName = new DynamicInputModel({
|
this.groupName = new DynamicInputModel({
|
||||||
id: 'groupName',
|
id: 'groupName',
|
||||||
@@ -123,21 +165,26 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.formModel = [
|
this.formModel = [
|
||||||
this.groupName,
|
this.groupName,
|
||||||
this.groupDescription
|
this.groupDescription,
|
||||||
];
|
];
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
this.subs.push(
|
||||||
if (activeGroup != null) {
|
observableCombineLatest(
|
||||||
this.groupBeingEdited = activeGroup;
|
this.groupDataService.getActiveGroup(),
|
||||||
this.formGroup.patchValue({
|
this.canEdit$
|
||||||
groupName: activeGroup != null ? activeGroup.name : '',
|
).subscribe(([activeGroup, canEdit]) => {
|
||||||
groupDescription: activeGroup != null ? activeGroup.firstMetadataValue('dc.description') : '',
|
if (activeGroup != null) {
|
||||||
});
|
this.groupBeingEdited = activeGroup;
|
||||||
if (activeGroup.permanent) {
|
this.formGroup.patchValue({
|
||||||
this.formGroup.get('groupName').disable();
|
groupName: activeGroup != null ? activeGroup.name : '',
|
||||||
|
groupDescription: activeGroup != null ? activeGroup.firstMetadataValue('dc.description') : '',
|
||||||
|
});
|
||||||
|
if (!canEdit || activeGroup.permanent) {
|
||||||
|
this.formGroup.disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}));
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +219,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
if (group === null) {
|
if (group === null) {
|
||||||
this.createNewGroup(values);
|
this.createNewGroup(values);
|
||||||
} else {
|
} else {
|
||||||
this.editGroup(group, values);
|
this.editGroup(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -193,6 +240,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
if (isNotEmpty(resp.resourceSelfLinks)) {
|
if (isNotEmpty(resp.resourceSelfLinks)) {
|
||||||
const groupSelfLink = resp.resourceSelfLinks[0];
|
const groupSelfLink = resp.resourceSelfLinks[0];
|
||||||
this.setActiveGroupWithLink(groupSelfLink);
|
this.setActiveGroupWithLink(groupSelfLink);
|
||||||
|
this.groupDataService.clearGroupsRequests();
|
||||||
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(this.groupDataService.getUUIDFromString(groupSelfLink)));
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(this.groupDataService.getUUIDFromString(groupSelfLink)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -225,14 +273,32 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // TODO
|
* Edit existing Group based on given values from form and old Group
|
||||||
* @param group
|
* @param group Group to edit and old values contained within
|
||||||
* @param values
|
|
||||||
*/
|
*/
|
||||||
editGroup(group: Group, values) {
|
editGroup(group: Group) {
|
||||||
// TODO (backend)
|
const editedGroup = Object.assign(new Group(), {
|
||||||
console.log('TODO implement editGroup', values);
|
id: group.id,
|
||||||
this.notificationsService.error('TODO implement editGroup (not yet implemented in backend) ');
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: (hasValue(this.groupDescription.value) ? this.groupDescription.value : group.firstMetadataValue('dc.description'))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
name: (hasValue(this.groupName.value) ? this.groupName.value : group.name),
|
||||||
|
_links: group._links,
|
||||||
|
});
|
||||||
|
const response = this.groupDataService.updateGroup(editedGroup);
|
||||||
|
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
|
||||||
|
if (restResponse.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.edited.success', { name: editedGroup.name }));
|
||||||
|
this.submitForm.emit(editedGroup);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.edited.failure', { name: editedGroup.name }));
|
||||||
|
this.cancelForm.emit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +324,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
if (activeGroup === null) {
|
if (activeGroup === null) {
|
||||||
this.groupDataService.cancelEditGroup();
|
this.groupDataService.cancelEditGroup();
|
||||||
this.groupDataService.findByHref(groupSelfLink)
|
this.groupDataService.findByHref(groupSelfLink, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
||||||
.pipe(
|
.pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
getRemoteDataPayload())
|
getRemoteDataPayload())
|
||||||
@@ -269,6 +335,48 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the Group from the Repository. The Group will be the only that this form is showing.
|
||||||
|
* It'll either show a success or error message depending on whether the delete was successful or not.
|
||||||
|
*/
|
||||||
|
delete() {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((group: Group) => {
|
||||||
|
const modalRef = this.modalService.open(ConfirmationModalComponent);
|
||||||
|
modalRef.componentInstance.dso = group;
|
||||||
|
modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header';
|
||||||
|
modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-group.modal.info';
|
||||||
|
modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-group.modal.cancel';
|
||||||
|
modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-group.modal.confirm';
|
||||||
|
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
|
||||||
|
if (confirm) {
|
||||||
|
if (hasValue(group.id)) {
|
||||||
|
this.groupDataService.deleteGroup(group).pipe(take(1))
|
||||||
|
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => {
|
||||||
|
if (success) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: group.name }));
|
||||||
|
this.reset();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(
|
||||||
|
this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: group.name }),
|
||||||
|
this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content', { cause: optionalErrorMessage }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will ensure that the page gets reset and that the cache is cleared
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.groupDataService.getBrowseEndpoint().pipe(take(1)).subscribe((href: string) => {
|
||||||
|
this.requestService.removeByHrefSubstring(href);
|
||||||
|
});
|
||||||
|
this.onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
*/
|
*/
|
||||||
@@ -277,4 +385,58 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
this.onCancel();
|
this.onCancel();
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
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
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(() => observableOf(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,10 +30,10 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
[pageInfoState]="(groups | async)?.payload"
|
[pageInfoState]="pageInfoState$"
|
||||||
[collectionSize]="(groups | async)?.payload?.totalElements"
|
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||||
[hideGear]="true"
|
[hideGear]="true"
|
||||||
[hidePagerWhenSinglePage]="true"
|
[hidePagerWhenSinglePage]="true"
|
||||||
(pageChange)="onPageChange($event)">
|
(pageChange)="onPageChange($event)">
|
||||||
@@ -50,21 +50,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let group of (groups | async)?.payload?.page">
|
<tr *ngFor="let groupDto of (groupsDto$ | async)?.page">
|
||||||
<td>{{group.id}}</td>
|
<td>{{groupDto.group.id}}</td>
|
||||||
<td>{{group.name}}</td>
|
<td>{{groupDto.group.name}}</td>
|
||||||
<td>{{(getMembers(group) | async)?.payload?.totalElements + (getSubgroups(group) | async)?.payload?.totalElements}}</td>
|
<td>{{(getMembers(groupDto.group) | async)?.payload?.totalElements + (getSubgroups(groupDto.group) | async)?.payload?.totalElements}}</td>
|
||||||
<!-- <td>{{getOptionalComColFromName(group.name)}}</td>-->
|
<!-- <td>{{getOptionalComColFromName(group.name)}}</td>-->
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button [routerLink]="groupService.getGroupEditPageRouterLink(group)"
|
<button [routerLink]="groupService.getGroupEditPageRouterLink(groupDto.group)"
|
||||||
class="btn btn-outline-primary btn-sm"
|
class="btn btn-outline-primary btn-sm"
|
||||||
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: group.name} }}">
|
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: groupDto.group.name} }}">
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="!group?.permanent" (click)="deleteGroup(group)"
|
<button *ngIf="!groupDto.group?.permanent && groupDto.ableToDelete"
|
||||||
class="btn btn-outline-danger btn-sm"
|
(click)="deleteGroup(groupDto.group)" class="btn btn-outline-danger btn-sm"
|
||||||
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: group.name} }}">
|
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: groupDto.group.name} }}">
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ds-pagination>
|
</ds-pagination>
|
||||||
|
|
||||||
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
<div *ngIf="(pageInfoState$ | async)?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
{{messagePrefix + 'no-items' | translate}}
|
{{messagePrefix + 'no-items' | translate}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -6,14 +6,18 @@ import { BrowserModule, By } from '@angular/platform-browser';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
||||||
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
import { Group } from '../../../core/eperson/models/group.model';
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
import { RouteService } from '../../../core/services/route.service';
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
|
import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
|
||||||
@@ -30,6 +34,8 @@ describe('GroupRegistryComponent', () => {
|
|||||||
let fixture: ComponentFixture<GroupsRegistryComponent>;
|
let fixture: ComponentFixture<GroupsRegistryComponent>;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
|
let dsoDataServiceStub: any;
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
|
||||||
let mockGroups;
|
let mockGroups;
|
||||||
let mockEPeople;
|
let mockEPeople;
|
||||||
@@ -41,11 +47,11 @@ describe('GroupRegistryComponent', () => {
|
|||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
switch (href) {
|
switch (href) {
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons':
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, [EPersonMock]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [EPersonMock]));
|
||||||
default:
|
default:
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -54,11 +60,11 @@ describe('GroupRegistryComponent', () => {
|
|||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
switch (href) {
|
switch (href) {
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups':
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, [GroupMock2]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [GroupMock2]));
|
||||||
default:
|
default:
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getGroupEditPageRouterLink(group: Group): string {
|
getGroupEditPageRouterLink(group: Group): string {
|
||||||
@@ -69,14 +75,22 @@ describe('GroupRegistryComponent', () => {
|
|||||||
},
|
},
|
||||||
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allGroups));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allGroups.length, totalElements: this.allGroups.length, totalPages: 1, currentPage: 1 }), this.allGroups));
|
||||||
}
|
}
|
||||||
const result = this.allGroups.find((group: Group) => {
|
const result = this.allGroups.find((group: Group) => {
|
||||||
return (group.id.includes(query))
|
return (group.id.includes(query))
|
||||||
});
|
});
|
||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
dsoDataServiceStub = {
|
||||||
|
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
|
isAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
@@ -90,9 +104,12 @@ describe('GroupRegistryComponent', () => {
|
|||||||
providers: [GroupsRegistryComponent,
|
providers: [GroupsRegistryComponent,
|
||||||
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: dsoDataServiceStub },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
{ provide: RouteService, useValue: routeServiceStub },
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
{ provide: Router, useValue: new RouterMock() },
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
|
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])}
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -1,16 +1,26 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { FormBuilder } from '@angular/forms';
|
import { FormBuilder } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription, Observable, of as observableOf } from 'rxjs';
|
||||||
import { take } from 'rxjs/operators';
|
import { filter } from 'rxjs/internal/operators/filter';
|
||||||
|
import { ObservedValueOf } from 'rxjs/internal/types';
|
||||||
|
import { catchError, 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';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
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 { Group } from '../../../core/eperson/models/group.model';
|
||||||
import { RouteService } from '../../../core/services/route.service';
|
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';
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -23,7 +33,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
|||||||
* A component used for managing all existing groups within the repository.
|
* A component used for managing all existing groups within the repository.
|
||||||
* The admin can create, edit or delete groups here.
|
* The admin can create, edit or delete groups here.
|
||||||
*/
|
*/
|
||||||
export class GroupsRegistryComponent implements OnInit {
|
export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
messagePrefix = 'admin.access-control.groups.';
|
messagePrefix = 'admin.access-control.groups.';
|
||||||
|
|
||||||
@@ -37,9 +47,19 @@ export class GroupsRegistryComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the current groups within the repository or the result of the search
|
* A list of all the current Groups within the repository or the result of the search
|
||||||
*/
|
*/
|
||||||
groups: Observable<RemoteData<PaginatedList<Group>>>;
|
groups$: BehaviorSubject<RemoteData<PaginatedList<Group>>> = new BehaviorSubject<RemoteData<PaginatedList<Group>>>({} as any);
|
||||||
|
/**
|
||||||
|
* A BehaviorSubject with the list of GroupDtoModel objects made from the Groups in the repository or
|
||||||
|
* as the result of the search
|
||||||
|
*/
|
||||||
|
groupsDto$: BehaviorSubject<PaginatedList<GroupDtoModel>> = new BehaviorSubject<PaginatedList<GroupDtoModel>>({} as any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable for the pageInfo, needed to pass to the pagination component
|
||||||
|
*/
|
||||||
|
pageInfoState$: BehaviorSubject<PageInfo> = new BehaviorSubject<PageInfo>(undefined);
|
||||||
|
|
||||||
// The search form
|
// The search form
|
||||||
searchForm;
|
searchForm;
|
||||||
@@ -47,13 +67,21 @@ export class GroupsRegistryComponent implements OnInit {
|
|||||||
// Current search in groups registry
|
// Current search in groups registry
|
||||||
currentSearchQuery: string;
|
currentSearchQuery: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(public groupService: GroupDataService,
|
constructor(public groupService: GroupDataService,
|
||||||
private ePersonDataService: EPersonDataService,
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
private router: Router) {
|
private router: Router,
|
||||||
|
private authorizationService: AuthorizationDataService,
|
||||||
|
public requestService: RequestService) {
|
||||||
this.currentSearchQuery = '';
|
this.currentSearchQuery = '';
|
||||||
this.searchForm = this.formBuilder.group(({
|
this.searchForm = this.formBuilder.group(({
|
||||||
query: this.currentSearchQuery,
|
query: this.currentSearchQuery,
|
||||||
@@ -84,37 +112,69 @@ export class GroupsRegistryComponent implements OnInit {
|
|||||||
this.currentSearchQuery = query;
|
this.currentSearchQuery = query;
|
||||||
this.config.currentPage = 1;
|
this.config.currentPage = 1;
|
||||||
}
|
}
|
||||||
this.groups = this.groupService.searchGroups(this.currentSearchQuery.trim(), {
|
this.subs.push(this.groupService.searchGroups(this.currentSearchQuery.trim(), {
|
||||||
currentPage: this.config.currentPage,
|
currentPage: this.config.currentPage,
|
||||||
elementsPerPage: this.config.pageSize
|
elementsPerPage: this.config.pageSize
|
||||||
});
|
}).subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
|
||||||
|
this.groups$.next(groupsRD);
|
||||||
|
this.pageInfoState$.next(groupsRD.payload.pageInfo);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
this.subs.push(this.groups$.pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
switchMap((groups: PaginatedList<Group>) => {
|
||||||
|
return observableCombineLatest(...groups.page.map((group: Group) => {
|
||||||
|
return observableCombineLatest(
|
||||||
|
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 = isAuthorized && !hasLinkedDSO;
|
||||||
|
groupDtoModel.group = group;
|
||||||
|
return groupDtoModel;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})).pipe(map((dtos: GroupDtoModel[]) => {
|
||||||
|
return new PaginatedList(groups.pageInfo, dtos);
|
||||||
|
}))
|
||||||
|
})).subscribe((value: PaginatedList<GroupDtoModel>) => {
|
||||||
|
this.groupsDto$.next(value);
|
||||||
|
this.pageInfoState$.next(value.pageInfo);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Group
|
* Delete Group
|
||||||
*/
|
*/
|
||||||
deleteGroup(group: Group) {
|
deleteGroup(group: Group) {
|
||||||
// TODO (backend)
|
|
||||||
console.log('TODO implement editGroup', group);
|
|
||||||
this.notificationsService.error('TODO implement deleteGroup (not yet implemented in backend)');
|
|
||||||
if (hasValue(group.id)) {
|
if (hasValue(group.id)) {
|
||||||
this.groupService.deleteGroup(group).pipe(take(1)).subscribe((success: boolean) => {
|
this.groupService.deleteGroup(group).pipe(take(1))
|
||||||
if (success) {
|
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => {
|
||||||
this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name }));
|
if (success) {
|
||||||
this.forceUpdateGroup();
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name }));
|
||||||
} else {
|
this.reset();
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + 'notification.deleted.failure', { name: group.name }));
|
} else {
|
||||||
}
|
this.notificationsService.error(
|
||||||
})
|
this.translateService.get(this.messagePrefix + 'notification.deleted.failure.title', { name: group.name }),
|
||||||
|
this.translateService.get(this.messagePrefix + 'notification.deleted.failure.content', { cause: optionalErrorMessage }));
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force-update the list of groups by first clearing the cache related to groups, then performing a new REST call
|
* This method will ensure that the page gets reset and that the cache is cleared
|
||||||
*/
|
*/
|
||||||
public forceUpdateGroup() {
|
reset() {
|
||||||
this.groupService.clearGroupsRequests();
|
this.groupService.getBrowseEndpoint().pipe(
|
||||||
this.search({ query: this.currentSearchQuery })
|
switchMap((href) => this.requestService.removeByHrefSubstring(href)),
|
||||||
|
filter((isCached) => isCached),
|
||||||
|
take(1)
|
||||||
|
).subscribe(() => {
|
||||||
|
this.cleanupSubscribes();
|
||||||
|
this.search({ query: this.currentSearchQuery });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,6 +193,23 @@ export class GroupsRegistryComponent implements OnInit {
|
|||||||
return this.groupService.findAllByHref(group._links.subgroups.href);
|
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> {
|
||||||
|
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
|
||||||
|
map((rd: RemoteData<DSpaceObject>) => {
|
||||||
|
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(() => observableOf(false)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all input-fields to be empty and search all search
|
* Reset all input-fields to be empty and search all search
|
||||||
*/
|
*/
|
||||||
@@ -151,4 +228,15 @@ export class GroupsRegistryComponent implements OnInit {
|
|||||||
getOptionalComColFromName(groupName: string): string {
|
getOptionalComColFromName(groupName: string): string {
|
||||||
return this.groupService.getUUIDFromString(groupName);
|
return this.groupService.getUUIDFromString(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.cleanupSubscribes();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupSubscribes() {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import { WorkflowItem } from '../../../../../core/submission/models/workflowitem
|
|||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { PublicationGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
|
import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
||||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||||
@@ -43,7 +43,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
|||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule(
|
TestBed.configureTestingModule(
|
||||||
{
|
{
|
||||||
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, PublicationGridElementComponent, ListableObjectDirective],
|
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
|
||||||
imports: [
|
imports: [
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
@@ -60,7 +60,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
|||||||
})
|
})
|
||||||
.overrideComponent(WorkflowItemSearchResultAdminWorkflowGridElementComponent, {
|
.overrideComponent(WorkflowItemSearchResultAdminWorkflowGridElementComponent, {
|
||||||
set: {
|
set: {
|
||||||
entryComponents: [PublicationGridElementComponent]
|
entryComponents: [ItemGridElementComponent]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -7,6 +7,7 @@ import { of as observableOf } from 'rxjs';
|
|||||||
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard';
|
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -17,8 +18,9 @@ import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
|||||||
export class CollectionPageAdministratorGuard extends DsoPageFeatureGuard<Collection> {
|
export class CollectionPageAdministratorGuard extends DsoPageFeatureGuard<Collection> {
|
||||||
constructor(protected resolver: CollectionPageResolver,
|
constructor(protected resolver: CollectionPageResolver,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(resolver, authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -20,6 +20,11 @@ export function getCollectionCreateRoute() {
|
|||||||
return new URLCombiner(getCollectionModuleRoute(), COLLECTION_CREATE_PATH).toString()
|
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_CREATE_PATH = 'create';
|
||||||
export const COLLECTION_EDIT_PATH = 'edit';
|
export const COLLECTION_EDIT_PATH = 'edit';
|
||||||
|
export const COLLECTION_EDIT_ROLES_PATH = 'roles';
|
||||||
export const ITEMTEMPLATE_PATH = 'itemtemplate';
|
export const ITEMTEMPLATE_PATH = 'itemtemplate';
|
||||||
|
@@ -17,13 +17,14 @@ import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
|||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
import {
|
import {
|
||||||
getSucceededRemoteData,
|
getSucceededRemoteData,
|
||||||
redirectOn404Or401,
|
redirectOn4xx,
|
||||||
toDSpaceObjectListRD
|
toDSpaceObjectListRD
|
||||||
} from '../core/shared/operators';
|
} from '../core/shared/operators';
|
||||||
|
|
||||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-page',
|
selector: 'ds-collection-page',
|
||||||
@@ -51,7 +52,8 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private metadata: MetadataService,
|
private metadata: MetadataService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private authService: AuthService,
|
||||||
) {
|
) {
|
||||||
this.paginationConfig = new PaginationComponentOptions();
|
this.paginationConfig = new PaginationComponentOptions();
|
||||||
this.paginationConfig.id = 'collection-page-pagination';
|
this.paginationConfig.id = 'collection-page-pagination';
|
||||||
@@ -63,7 +65,7 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.collectionRD$ = this.route.data.pipe(
|
this.collectionRD$ = this.route.data.pipe(
|
||||||
map((data) => data.dso as RemoteData<Collection>),
|
map((data) => data.dso as RemoteData<Collection>),
|
||||||
redirectOn404Or401(this.router),
|
redirectOn4xx(this.router, this.authService),
|
||||||
take(1)
|
take(1)
|
||||||
);
|
);
|
||||||
this.logoRD$ = this.collectionRD$.pipe(
|
this.logoRD$ = this.collectionRD$.pipe(
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-resource-policies [resourceType]="'collection'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
|
||||||
|
</div>
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { CollectionAuthorizationsComponent } from './collection-authorizations.component';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
|
||||||
|
describe('CollectionAuthorizationsComponent', () => {
|
||||||
|
let comp: CollectionAuthorizationsComponent<DSpaceObject>;
|
||||||
|
let fixture: ComponentFixture<CollectionAuthorizationsComponent<any>>;
|
||||||
|
|
||||||
|
const collection = Object.assign(new Collection(), {
|
||||||
|
uuid: 'collection',
|
||||||
|
id: 'collection',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'collection-selflink' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const collectionRD = createSuccessfulRemoteDataObject(collection);
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
data: observableOf({
|
||||||
|
dso: collectionRD
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
declarations: [CollectionAuthorizationsComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
CollectionAuthorizationsComponent,
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CollectionAuthorizationsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(comp).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init dso remote data properly', (done) => {
|
||||||
|
const expected = cold('(a|)', { a: collectionRD });
|
||||||
|
expect(comp.dsoRD$).toBeObservable(expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { first, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-authorizations',
|
||||||
|
templateUrl: './collection-authorizations.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that handles the Collection Authorizations
|
||||||
|
*/
|
||||||
|
export class CollectionAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial DSO object
|
||||||
|
*/
|
||||||
|
public dsoRD$: Observable<RemoteData<TDomain>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the collection
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,7 @@ import { CollectionPageModule } from '../collection-page.module';
|
|||||||
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
||||||
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
||||||
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
||||||
|
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Collection page administrator functionality
|
* Module that contains all components related to the Edit Collection page administrator functionality
|
||||||
@@ -24,7 +25,8 @@ import { CollectionSourceComponent } from './collection-source/collection-source
|
|||||||
CollectionMetadataComponent,
|
CollectionMetadataComponent,
|
||||||
CollectionRolesComponent,
|
CollectionRolesComponent,
|
||||||
CollectionCurateComponent,
|
CollectionCurateComponent,
|
||||||
CollectionSourceComponent
|
CollectionSourceComponent,
|
||||||
|
CollectionAuthorizationsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class EditCollectionPageModule {
|
export class EditCollectionPageModule {
|
||||||
|
@@ -5,7 +5,12 @@ import { CollectionMetadataComponent } from './collection-metadata/collection-me
|
|||||||
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
||||||
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
||||||
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
||||||
|
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing module that handles the routing for the Edit Collection page administrator functionality
|
* Routing module that handles the routing for the Edit Collection page administrator functionality
|
||||||
@@ -49,10 +54,46 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
|
|||||||
path: 'curate',
|
path: 'curate',
|
||||||
component: CollectionCurateComponent,
|
component: CollectionCurateComponent,
|
||||||
data: { title: 'collection.edit.tabs.curate.title', showBreadcrumbs: true }
|
data: { title: 'collection.edit.tabs.curate.title', showBreadcrumbs: true }
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
path: 'authorizations',
|
||||||
|
component: CollectionAuthorizationsComponent,
|
||||||
|
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
|
||||||
|
},*/
|
||||||
|
{
|
||||||
|
path: 'authorizations',
|
||||||
|
data: { showBreadcrumbs: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicyTarget: ResourcePolicyTargetResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyCreateComponent,
|
||||||
|
data: { title: 'resource-policies.create.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicy: ResourcePolicyResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyEditComponent,
|
||||||
|
data: { title: 'resource-policies.edit.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CollectionAuthorizationsComponent,
|
||||||
|
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class EditCollectionPageRoutingModule {
|
export class EditCollectionPageRoutingModule {
|
||||||
|
@@ -7,6 +7,7 @@ import { of as observableOf } from 'rxjs';
|
|||||||
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard';
|
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -17,8 +18,9 @@ import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
|||||||
export class CommunityPageAdministratorGuard extends DsoPageFeatureGuard<Community> {
|
export class CommunityPageAdministratorGuard extends DsoPageFeatureGuard<Community> {
|
||||||
constructor(protected resolver: CommunityPageResolver,
|
constructor(protected resolver: CommunityPageResolver,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(resolver, authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { getCollectionPageRoute } from '../+collection-page/collection-page-routing-paths';
|
||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
export const COMMUNITY_PARENT_PARAMETER = 'parent';
|
export const COMMUNITY_PARENT_PARAMETER = 'parent';
|
||||||
@@ -20,5 +21,10 @@ export function getCommunityCreateRoute() {
|
|||||||
return new URLCombiner(getCommunityModuleRoute(), COMMUNITY_CREATE_PATH).toString()
|
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_CREATE_PATH = 'create';
|
||||||
export const COMMUNITY_EDIT_PATH = 'edit';
|
export const COMMUNITY_EDIT_PATH = 'edit';
|
||||||
|
export const COMMUNITY_EDIT_ROLES_PATH = 'roles';
|
||||||
|
@@ -13,7 +13,8 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
|||||||
|
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { redirectOn404Or401 } from '../core/shared/operators';
|
import { redirectOn4xx } from '../core/shared/operators';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page',
|
selector: 'ds-community-page',
|
||||||
@@ -39,7 +40,8 @@ export class CommunityPageComponent implements OnInit {
|
|||||||
private communityDataService: CommunityDataService,
|
private communityDataService: CommunityDataService,
|
||||||
private metadata: MetadataService,
|
private metadata: MetadataService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private authService: AuthService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -47,7 +49,7 @@ export class CommunityPageComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.communityRD$ = this.route.data.pipe(
|
this.communityRD$ = this.route.data.pipe(
|
||||||
map((data) => data.dso as RemoteData<Community>),
|
map((data) => data.dso as RemoteData<Community>),
|
||||||
redirectOn404Or401(this.router)
|
redirectOn4xx(this.router, this.authService)
|
||||||
);
|
);
|
||||||
this.logoRD$ = this.communityRD$.pipe(
|
this.logoRD$ = this.communityRD$.pipe(
|
||||||
map((rd: RemoteData<Community>) => rd.payload),
|
map((rd: RemoteData<Community>) => rd.payload),
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-resource-policies [resourceType]="'community'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
|
||||||
|
</div>
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { CommunityAuthorizationsComponent } from './community-authorizations.component';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
|
||||||
|
describe('CommunityAuthorizationsComponent', () => {
|
||||||
|
let comp: CommunityAuthorizationsComponent<DSpaceObject>;
|
||||||
|
let fixture: ComponentFixture<CommunityAuthorizationsComponent<any>>;
|
||||||
|
|
||||||
|
const community = Object.assign(new Collection(), {
|
||||||
|
uuid: 'community',
|
||||||
|
id: 'community',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'community-selflink' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const communityRD = createSuccessfulRemoteDataObject(community);
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
data: observableOf({
|
||||||
|
dso: communityRD
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
declarations: [CommunityAuthorizationsComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
CommunityAuthorizationsComponent,
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityAuthorizationsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(comp).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init dso remote data properly', (done) => {
|
||||||
|
const expected = cold('(a|)', { a: communityRD });
|
||||||
|
expect(comp.dsoRD$).toBeObservable(expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,38 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { first, map } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from 'src/app/core/data/remote-data';
|
||||||
|
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-community-authorizations',
|
||||||
|
templateUrl: './community-authorizations.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that handles the community Authorizations
|
||||||
|
*/
|
||||||
|
export class CommunityAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial DSO object
|
||||||
|
*/
|
||||||
|
public dsoRD$: Observable<RemoteData<TDomain>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the community
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ import { EditCommunityPageComponent } from './edit-community-page.component';
|
|||||||
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
||||||
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
|
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
|
||||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||||
|
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Community page administrator functionality
|
* Module that contains all components related to the Edit Community page administrator functionality
|
||||||
@@ -22,7 +23,8 @@ import { CommunityRolesComponent } from './community-roles/community-roles.compo
|
|||||||
EditCommunityPageComponent,
|
EditCommunityPageComponent,
|
||||||
CommunityCurateComponent,
|
CommunityCurateComponent,
|
||||||
CommunityMetadataComponent,
|
CommunityMetadataComponent,
|
||||||
CommunityRolesComponent
|
CommunityRolesComponent,
|
||||||
|
CommunityAuthorizationsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class EditCommunityPageModule {
|
export class EditCommunityPageModule {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { CommunityPageResolver } from '../community-page.resolver';
|
|
||||||
import { EditCommunityPageComponent } from './edit-community-page.component';
|
import { EditCommunityPageComponent } from './edit-community-page.component';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
@@ -6,6 +5,11 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
|
|||||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||||
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
||||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||||
|
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing module that handles the routing for the Edit Community page administrator functionality
|
* Routing module that handles the routing for the Edit Community page administrator functionality
|
||||||
@@ -44,11 +48,47 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
|
|||||||
path: 'curate',
|
path: 'curate',
|
||||||
component: CommunityCurateComponent,
|
component: CommunityCurateComponent,
|
||||||
data: { title: 'community.edit.tabs.curate.title', showBreadcrumbs: true }
|
data: { title: 'community.edit.tabs.curate.title', showBreadcrumbs: true }
|
||||||
|
},
|
||||||
|
/*{
|
||||||
|
path: 'authorizations',
|
||||||
|
component: CommunityAuthorizationsComponent,
|
||||||
|
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true }
|
||||||
|
},*/
|
||||||
|
{
|
||||||
|
path: 'authorizations',
|
||||||
|
data: { showBreadcrumbs: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicyTarget: ResourcePolicyTargetResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyCreateComponent,
|
||||||
|
data: { title: 'resource-policies.create.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicy: ResourcePolicyResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyEditComponent,
|
||||||
|
data: { title: 'resource-policies.edit.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CommunityAuthorizationsComponent,
|
||||||
|
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true, hideReturnButton: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class EditCommunityPageRoutingModule {
|
export class EditCommunityPageRoutingModule {
|
||||||
|
|
||||||
|
@@ -30,8 +30,6 @@ import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/it
|
|||||||
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
||||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||||
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||||
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
|
||||||
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
|
||||||
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,9 +69,7 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
|||||||
ItemMoveComponent,
|
ItemMoveComponent,
|
||||||
ItemEditBitstreamDragHandleComponent,
|
ItemEditBitstreamDragHandleComponent,
|
||||||
VirtualMetadataComponent,
|
VirtualMetadataComponent,
|
||||||
ItemAuthorizationsComponent,
|
ItemAuthorizationsComponent
|
||||||
ResourcePolicyEditComponent,
|
|
||||||
ResourcePolicyCreateComponent,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BundleDataService,
|
BundleDataService,
|
||||||
|
@@ -7,6 +7,7 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -17,8 +18,9 @@ import { of as observableOf } from 'rxjs';
|
|||||||
export class ItemPageReinstateGuard extends DsoPageFeatureGuard<Item> {
|
export class ItemPageReinstateGuard extends DsoPageFeatureGuard<Item> {
|
||||||
constructor(protected resolver: ItemPageResolver,
|
constructor(protected resolver: ItemPageResolver,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(resolver, authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -7,6 +7,7 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -17,8 +18,9 @@ import { of as observableOf } from 'rxjs';
|
|||||||
export class ItemPageWithdrawGuard extends DsoPageFeatureGuard<Item> {
|
export class ItemPageWithdrawGuard extends DsoPageFeatureGuard<Item> {
|
||||||
constructor(protected resolver: ItemPageResolver,
|
constructor(protected resolver: ItemPageResolver,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(resolver, authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -20,6 +20,7 @@ import {
|
|||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
createSuccessfulRemoteDataObject$
|
createSuccessfulRemoteDataObject$
|
||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
@@ -45,7 +46,14 @@ describe('FullItemPageComponent', () => {
|
|||||||
let comp: FullItemPageComponent;
|
let comp: FullItemPageComponent;
|
||||||
let fixture: ComponentFixture<FullItemPageComponent>;
|
let fixture: ComponentFixture<FullItemPageComponent>;
|
||||||
|
|
||||||
|
let authService: AuthService;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true),
|
||||||
|
setRedirectUrl: {}
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -57,7 +65,8 @@ describe('FullItemPageComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{provide: ActivatedRoute, useValue: routeStub},
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: MetadataService, useValue: metadataServiceStub}
|
{provide: MetadataService, useValue: metadataServiceStub},
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -15,6 +15,7 @@ import { MetadataService } from '../../core/metadata/metadata.service';
|
|||||||
|
|
||||||
import { fadeInOut } from '../../shared/animations/fade';
|
import { fadeInOut } from '../../shared/animations/fade';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -35,8 +36,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
|||||||
|
|
||||||
metadata$: Observable<MetadataMap>;
|
metadata$: Observable<MetadataMap>;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService) {
|
constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService, authService: AuthService) {
|
||||||
super(route, router, items, metadataService);
|
super(route, router, items, metadataService, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||||
|
@@ -7,6 +7,7 @@ import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -17,8 +18,9 @@ import { of as observableOf } from 'rxjs';
|
|||||||
export class ItemPageAdministratorGuard extends DsoPageFeatureGuard<Item> {
|
export class ItemPageAdministratorGuard extends DsoPageFeatureGuard<Item> {
|
||||||
constructor(protected resolver: ItemPageResolver,
|
constructor(protected resolver: ItemPageResolver,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(resolver, authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -30,6 +30,7 @@ import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.c
|
|||||||
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
|
import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component';
|
||||||
|
|
||||||
import { MediaViewerComponent } from './media-viewer/media-viewer.component';
|
import { MediaViewerComponent } from './media-viewer/media-viewer.component';
|
||||||
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
|
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
|
||||||
@@ -60,6 +61,7 @@ import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
|||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
FullFileSectionComponent,
|
FullFileSectionComponent,
|
||||||
PublicationComponent,
|
PublicationComponent,
|
||||||
|
UntypedItemComponent,
|
||||||
RelatedItemsComponent,
|
RelatedItemsComponent,
|
||||||
ItemComponent,
|
ItemComponent,
|
||||||
GenericItemPageFieldComponent,
|
GenericItemPageFieldComponent,
|
||||||
@@ -83,6 +85,9 @@ import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
|||||||
ItemPageTitleFieldComponent,
|
ItemPageTitleFieldComponent,
|
||||||
TabbedRelatedEntitiesSearchComponent,
|
TabbedRelatedEntitiesSearchComponent,
|
||||||
],
|
],
|
||||||
entryComponents: [PublicationComponent],
|
entryComponents: [
|
||||||
|
PublicationComponent,
|
||||||
|
UntypedItemComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class ItemPageModule {}
|
export class ItemPageModule {}
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
createFailedRemoteDataObject$, createPendingRemoteDataObject$, createSuccessfulRemoteDataObject,
|
createFailedRemoteDataObject$, createPendingRemoteDataObject$, createSuccessfulRemoteDataObject,
|
||||||
createSuccessfulRemoteDataObject$
|
createSuccessfulRemoteDataObject$
|
||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
@@ -29,6 +30,7 @@ const mockItem: Item = Object.assign(new Item(), {
|
|||||||
describe('ItemPageComponent', () => {
|
describe('ItemPageComponent', () => {
|
||||||
let comp: ItemPageComponent;
|
let comp: ItemPageComponent;
|
||||||
let fixture: ComponentFixture<ItemPageComponent>;
|
let fixture: ComponentFixture<ItemPageComponent>;
|
||||||
|
let authService: AuthService;
|
||||||
|
|
||||||
const mockMetadataService = {
|
const mockMetadataService = {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
@@ -40,6 +42,11 @@ describe('ItemPageComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true),
|
||||||
|
setRedirectUrl: {}
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -52,7 +59,8 @@ describe('ItemPageComponent', () => {
|
|||||||
{provide: ActivatedRoute, useValue: mockRoute},
|
{provide: ActivatedRoute, useValue: mockRoute},
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: MetadataService, useValue: mockMetadataService},
|
{provide: MetadataService, useValue: mockMetadataService},
|
||||||
{provide: Router, useValue: {}}
|
{provide: Router, useValue: {}},
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -11,8 +11,9 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||||
|
|
||||||
import { fadeInOut } from '../../shared/animations/fade';
|
import { fadeInOut } from '../../shared/animations/fade';
|
||||||
import { redirectOn404Or401 } from '../../core/shared/operators';
|
import { redirectOn4xx } from '../../core/shared/operators';
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -48,6 +49,7 @@ export class ItemPageComponent implements OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private items: ItemDataService,
|
private items: ItemDataService,
|
||||||
private metadataService: MetadataService,
|
private metadataService: MetadataService,
|
||||||
|
private authService: AuthService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +58,7 @@ export class ItemPageComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(
|
this.itemRD$ = this.route.data.pipe(
|
||||||
map((data) => data.dso as RemoteData<Item>),
|
map((data) => data.dso as RemoteData<Item>),
|
||||||
redirectOn404Or401(this.router)
|
redirectOn4xx(this.router, this.authService)
|
||||||
);
|
);
|
||||||
this.metadataService.processRemoteData(this.itemRD$);
|
this.metadataService.processRemoteData(this.itemRD$);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
|
||||||
import { ItemComponent } from '../shared/item.component';
|
import { ItemComponent } from '../shared/item.component';
|
||||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
@@ -9,7 +8,6 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@listableObjectComponent('Publication', ViewMode.StandalonePage)
|
@listableObjectComponent('Publication', ViewMode.StandalonePage)
|
||||||
@listableObjectComponent(Item, ViewMode.StandalonePage)
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-publication',
|
selector: 'ds-publication',
|
||||||
styleUrls: ['./publication.component.scss'],
|
styleUrls: ['./publication.component.scss'],
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
<div class="d-flex flex-row">
|
||||||
|
<h2 class="item-page-title-field mr-auto">
|
||||||
|
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||||
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
|
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['journal.title']"
|
||||||
|
[label]="'item.page.journal-title'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['journal.identifier.issn']"
|
||||||
|
[label]="'item.page.journal-issn'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['journalvolume.identifier.name']"
|
||||||
|
[label]="'item.page.volume-title'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['dc.publisher']"
|
||||||
|
[label]="'item.page.publisher'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-metadata-representation-list
|
||||||
|
[parentItem]="object"
|
||||||
|
[itemType]="'Person'"
|
||||||
|
[metadataField]="'dc.contributor.author'"
|
||||||
|
[label]="'relationships.isAuthorOf' | translate">
|
||||||
|
</ds-metadata-representation-list>
|
||||||
|
<ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['dc.description']"
|
||||||
|
[label]="'item.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['dc.subject']"
|
||||||
|
[separator]="','"
|
||||||
|
[label]="'item.page.subject'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
[fields]="['dc.identifier.citation']"
|
||||||
|
[label]="'item.page.citation'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-item-page-uri-field [item]="object"
|
||||||
|
[fields]="['dc.identifier.uri']"
|
||||||
|
[label]="'item.page.uri'">
|
||||||
|
</ds-item-page-uri-field>
|
||||||
|
<ds-item-page-collections [item]="object"></ds-item-page-collections>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + object.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,112 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
|
import { CommunityDataService } from '../../../../core/data/community-data.service';
|
||||||
|
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
|
||||||
|
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { UUIDService } from '../../../../core/shared/uuid.service';
|
||||||
|
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
|
||||||
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
|
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
||||||
|
import { UntypedItemComponent } from './untyped-item.component';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
|
metadata: new MetadataMap(),
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UntypedItemComponent', () => {
|
||||||
|
let comp: UntypedItemComponent;
|
||||||
|
let fixture: ComponentFixture<UntypedItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
const mockBitstreamDataService = {
|
||||||
|
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(new Bitstream());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [UntypedItemComponent, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
|
providers: [
|
||||||
|
{ provide: ItemDataService, useValue: {} },
|
||||||
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: RelationshipService, useValue: {} },
|
||||||
|
{ provide: ObjectCacheService, useValue: {} },
|
||||||
|
{ provide: UUIDService, useValue: {} },
|
||||||
|
{ provide: Store, useValue: {} },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
|
{ provide: CommunityDataService, useValue: {} },
|
||||||
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
|
{ provide: NotificationsService, useValue: {} },
|
||||||
|
{ provide: HttpClient, useValue: {} },
|
||||||
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(UntypedItemComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(UntypedItemComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = mockItem;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain a component to display the date', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the author', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the abstract', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the uri', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the collections', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,20 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that represents a publication Item page
|
||||||
|
*/
|
||||||
|
|
||||||
|
@listableObjectComponent(Item, ViewMode.StandalonePage)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-untyped-item',
|
||||||
|
styleUrls: ['./untyped-item.component.scss'],
|
||||||
|
templateUrl: './untyped-item.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class UntypedItemComponent extends ItemComponent {
|
||||||
|
|
||||||
|
}
|
@@ -15,7 +15,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
});
|
});
|
||||||
const mockRelationType = 'publicationsOfAuthor';
|
const mockRelationType = 'publicationsOfAuthor';
|
||||||
const mockConfiguration = 'publication';
|
const mockConfiguration = 'publication';
|
||||||
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
const mockFilter= `f.${mockRelationType}=${mockItem.id},equals`;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@@ -55,10 +55,10 @@ export function getDSORoute(dso: DSpaceObject): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UNAUTHORIZED_PATH = '401';
|
export const FORBIDDEN_PATH = '403';
|
||||||
|
|
||||||
export function getUnauthorizedRoute() {
|
export function getForbiddenRoute() {
|
||||||
return `/${UNAUTHORIZED_PATH}`;
|
return `/${FORBIDDEN_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PAGE_NOT_FOUND_PATH = '404';
|
export const PAGE_NOT_FOUND_PATH = '404';
|
||||||
|
@@ -5,16 +5,15 @@ import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
|||||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||||
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
|
|
||||||
import {
|
import {
|
||||||
UNAUTHORIZED_PATH,
|
|
||||||
WORKFLOW_ITEM_MODULE_PATH,
|
WORKFLOW_ITEM_MODULE_PATH,
|
||||||
FORGOT_PASSWORD_PATH,
|
FORGOT_PASSWORD_PATH,
|
||||||
REGISTER_PATH,
|
REGISTER_PATH,
|
||||||
PROFILE_MODULE_PATH,
|
PROFILE_MODULE_PATH,
|
||||||
ADMIN_MODULE_PATH,
|
ADMIN_MODULE_PATH,
|
||||||
BITSTREAM_MODULE_PATH,
|
BITSTREAM_MODULE_PATH,
|
||||||
INFO_MODULE_PATH
|
INFO_MODULE_PATH,
|
||||||
|
FORBIDDEN_PATH,
|
||||||
} from './app-routing-paths';
|
} from './app-routing-paths';
|
||||||
import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths';
|
import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths';
|
||||||
import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths';
|
import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths';
|
||||||
@@ -22,6 +21,7 @@ import { ITEM_MODULE_PATH } from './+item-page/item-page-routing-paths';
|
|||||||
import { ReloadGuard } from './core/reload/reload.guard';
|
import { ReloadGuard } from './core/reload/reload.guard';
|
||||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||||
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||||
|
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -68,7 +68,7 @@ import { SiteRegisterGuard } from './core/data/feature-authorization/feature-aut
|
|||||||
},
|
},
|
||||||
{ path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] },
|
{ path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] },
|
||||||
{ path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
|
{ path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
|
||||||
{ path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
|
{ path: FORBIDDEN_PATH, component: ForbiddenComponent },
|
||||||
{
|
{
|
||||||
path: 'statistics',
|
path: 'statistics',
|
||||||
loadChildren: './statistics-page/statistics-page-routing.module#StatisticsPageRoutingModule',
|
loadChildren: './statistics-page/statistics-page-routing.module#StatisticsPageRoutingModule',
|
||||||
|
@@ -41,7 +41,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
|
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||||
|
|
||||||
export function getBase() {
|
export function getBase() {
|
||||||
return environment.ui.nameSpace;
|
return environment.ui.nameSpace;
|
||||||
@@ -116,6 +116,8 @@ const DECLARATIONS = [
|
|||||||
NotificationComponent,
|
NotificationComponent,
|
||||||
NotificationsBoardComponent,
|
NotificationsBoardComponent,
|
||||||
SearchNavbarComponent,
|
SearchNavbarComponent,
|
||||||
|
BreadcrumbsComponent,
|
||||||
|
ForbiddenComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXPORTS = [
|
const EXPORTS = [
|
||||||
@@ -133,8 +135,6 @@ const EXPORTS = [
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS,
|
...DECLARATIONS,
|
||||||
BreadcrumbsComponent,
|
|
||||||
UnauthorizedComponent,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...EXPORTS
|
...EXPORTS
|
||||||
|
@@ -453,7 +453,7 @@ export class AuthService {
|
|||||||
* Clear redirect url
|
* Clear redirect url
|
||||||
*/
|
*/
|
||||||
clearRedirectUrl() {
|
clearRedirectUrl() {
|
||||||
this.store.dispatch(new SetRedirectUrlAction(''));
|
this.store.dispatch(new SetRedirectUrlAction(undefined));
|
||||||
this.storage.remove(REDIRECT_COOKIE);
|
this.storage.remove(REDIRECT_COOKIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
src/app/core/cache/object-cache.reducer.ts
vendored
15
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -33,6 +33,21 @@ export abstract class TypedObject {
|
|||||||
type: ResourceType;
|
type: ResourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string value for an object that may be a string or a ResourceType
|
||||||
|
*
|
||||||
|
* @param type the object to get the type value for
|
||||||
|
*/
|
||||||
|
export const getResourceTypeValueFor = (type: any): string => {
|
||||||
|
if (hasValue(type)) {
|
||||||
|
if (typeof type === 'string') {
|
||||||
|
return type;
|
||||||
|
} else if (typeof type.value === 'string') {
|
||||||
|
return type.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
/**
|
/**
|
||||||
* An interface to represent objects that can be cached
|
* An interface to represent objects that can be cached
|
||||||
|
@@ -58,4 +58,8 @@ export class DSpaceObjectDataService {
|
|||||||
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {
|
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
return this.dataService.findById(uuid);
|
return this.dataService.findById(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
return this.dataService.findByHref(href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -88,30 +88,10 @@ export class EntityTypeService extends DataService<ItemType> {
|
|||||||
* @param label
|
* @param label
|
||||||
*/
|
*/
|
||||||
getEntityTypeByLabel(label: string): Observable<RemoteData<ItemType>> {
|
getEntityTypeByLabel(label: string): Observable<RemoteData<ItemType>> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
// TODO: Remove mock data once REST API supports this
|
take(1),
|
||||||
/*
|
switchMap((endPoint: string) =>
|
||||||
href$.pipe(take(1)).subscribe((href) => {
|
this.findByHref(endPoint + '/label/' + label))
|
||||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
);
|
||||||
this.requestService.configure(request);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.rdbService.buildSingle<EntityType>(href$);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Mock:
|
|
||||||
const index = [
|
|
||||||
'Publication',
|
|
||||||
'Person',
|
|
||||||
'Project',
|
|
||||||
'OrgUnit',
|
|
||||||
'Journal',
|
|
||||||
'JournalVolume',
|
|
||||||
'JournalIssue',
|
|
||||||
'DataPackage',
|
|
||||||
'DataFile',
|
|
||||||
].indexOf(label);
|
|
||||||
|
|
||||||
return this.findById((index + 1) + '');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RemoteData } from '../remote-data';
|
import { RemoteData } from '../remote-data';
|
||||||
import { PaginatedList } from '../paginated-list';
|
import { PaginatedList } from '../paginated-list';
|
||||||
import { find, map, switchMap, tap } from 'rxjs/operators';
|
import { catchError, find, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
import { RequestParam } from '../../cache/models/request-param.model';
|
import { RequestParam } from '../../cache/models/request-param.model';
|
||||||
import { AuthorizationSearchParams } from './authorization-search-params';
|
import { AuthorizationSearchParams } from './authorization-search-params';
|
||||||
@@ -71,6 +71,7 @@ export class AuthorizationDataService extends DataService<Authorization> {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
catchError(() => observableOf(false)),
|
||||||
oneAuthorizationMatchesFeature(featureId)
|
oneAuthorizationMatchesFeature(featureId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import { DSpaceObject } from '../../../shared/dspace-object.model';
|
|||||||
import { DsoPageFeatureGuard } from './dso-page-feature.guard';
|
import { DsoPageFeatureGuard } from './dso-page-feature.guard';
|
||||||
import { FeatureID } from '../feature-id';
|
import { FeatureID } from '../feature-id';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test implementation of abstract class DsoPageAdministratorGuard
|
* Test implementation of abstract class DsoPageAdministratorGuard
|
||||||
@@ -15,8 +16,9 @@ class DsoPageFeatureGuardImpl extends DsoPageFeatureGuard<any> {
|
|||||||
constructor(protected resolver: Resolve<RemoteData<any>>,
|
constructor(protected resolver: Resolve<RemoteData<any>>,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
protected authService: AuthService,
|
||||||
protected featureID: FeatureID) {
|
protected featureID: FeatureID) {
|
||||||
super(resolver, authorizationService, router);
|
super(resolver, authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
@@ -28,6 +30,7 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
let guard: DsoPageFeatureGuard<any>;
|
let guard: DsoPageFeatureGuard<any>;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
|
let authService: AuthService;
|
||||||
let resolver: Resolve<RemoteData<any>>;
|
let resolver: Resolve<RemoteData<any>>;
|
||||||
let object: DSpaceObject;
|
let object: DSpaceObject;
|
||||||
|
|
||||||
@@ -45,7 +48,10 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
resolver = jasmine.createSpyObj('resolver', {
|
resolver = jasmine.createSpyObj('resolver', {
|
||||||
resolve: createSuccessfulRemoteDataObject$(object)
|
resolve: createSuccessfulRemoteDataObject$(object)
|
||||||
});
|
});
|
||||||
guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, undefined);
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true)
|
||||||
|
});
|
||||||
|
guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -6,6 +6,7 @@ import { getAllSucceededRemoteDataPayload } from '../../../shared/operators';
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
||||||
import { FeatureAuthorizationGuard } from './feature-authorization.guard';
|
import { FeatureAuthorizationGuard } from './feature-authorization.guard';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
|
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
|
||||||
@@ -14,8 +15,9 @@ import { FeatureAuthorizationGuard } from './feature-authorization.guard';
|
|||||||
export abstract class DsoPageFeatureGuard<T extends DSpaceObject> extends FeatureAuthorizationGuard {
|
export abstract class DsoPageFeatureGuard<T extends DSpaceObject> extends FeatureAuthorizationGuard {
|
||||||
constructor(protected resolver: Resolve<RemoteData<T>>,
|
constructor(protected resolver: Resolve<RemoteData<T>>,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(authorizationService, router);
|
protected authService: AuthService) {
|
||||||
|
super(authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -4,6 +4,7 @@ import { FeatureID } from '../feature-id';
|
|||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test implementation of abstract class FeatureAuthorizationGuard
|
* Test implementation of abstract class FeatureAuthorizationGuard
|
||||||
@@ -12,10 +13,11 @@ import { Observable } from 'rxjs/internal/Observable';
|
|||||||
class FeatureAuthorizationGuardImpl extends FeatureAuthorizationGuard {
|
class FeatureAuthorizationGuardImpl extends FeatureAuthorizationGuard {
|
||||||
constructor(protected authorizationService: AuthorizationDataService,
|
constructor(protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
protected authService: AuthService,
|
||||||
protected featureId: FeatureID,
|
protected featureId: FeatureID,
|
||||||
protected objectUrl: string,
|
protected objectUrl: string,
|
||||||
protected ePersonUuid: string) {
|
protected ePersonUuid: string) {
|
||||||
super(authorizationService, router);
|
super(authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
@@ -35,6 +37,7 @@ describe('FeatureAuthorizationGuard', () => {
|
|||||||
let guard: FeatureAuthorizationGuard;
|
let guard: FeatureAuthorizationGuard;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
|
let authService: AuthService;
|
||||||
|
|
||||||
let featureId: FeatureID;
|
let featureId: FeatureID;
|
||||||
let objectUrl: string;
|
let objectUrl: string;
|
||||||
@@ -51,7 +54,10 @@ describe('FeatureAuthorizationGuard', () => {
|
|||||||
router = jasmine.createSpyObj('router', {
|
router = jasmine.createSpyObj('router', {
|
||||||
parseUrl: {}
|
parseUrl: {}
|
||||||
});
|
});
|
||||||
guard = new FeatureAuthorizationGuardImpl(authorizationService, router, featureId, objectUrl, ePersonUuid);
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true)
|
||||||
|
});
|
||||||
|
guard = new FeatureAuthorizationGuardImpl(authorizationService, router, authService, featureId, objectUrl, ePersonUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -60,7 +66,7 @@ describe('FeatureAuthorizationGuard', () => {
|
|||||||
|
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
it('should call authorizationService.isAuthenticated with the appropriate arguments', () => {
|
it('should call authorizationService.isAuthenticated with the appropriate arguments', () => {
|
||||||
guard.canActivate(undefined, undefined).subscribe();
|
guard.canActivate(undefined, { url: 'current-url' } as any).subscribe();
|
||||||
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, objectUrl, ePersonUuid);
|
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, objectUrl, ePersonUuid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -8,9 +8,10 @@ import {
|
|||||||
import { AuthorizationDataService } from '../authorization-data.service';
|
import { AuthorizationDataService } from '../authorization-data.service';
|
||||||
import { FeatureID } from '../feature-id';
|
import { FeatureID } from '../feature-id';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { returnUnauthorizedUrlTreeOnFalse } from '../../../shared/operators';
|
import { returnForbiddenUrlTreeOrLoginOnFalse } from '../../../shared/operators';
|
||||||
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Guard for preventing unauthorized activating and loading of routes when a user
|
* Abstract Guard for preventing unauthorized activating and loading of routes when a user
|
||||||
@@ -19,7 +20,8 @@ import { switchMap } from 'rxjs/operators';
|
|||||||
*/
|
*/
|
||||||
export abstract class FeatureAuthorizationGuard implements CanActivate {
|
export abstract class FeatureAuthorizationGuard implements CanActivate {
|
||||||
constructor(protected authorizationService: AuthorizationDataService,
|
constructor(protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
|
protected authService: AuthService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +31,7 @@ export abstract class FeatureAuthorizationGuard implements CanActivate {
|
|||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||||
return observableCombineLatest(this.getFeatureID(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe(
|
return observableCombineLatest(this.getFeatureID(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe(
|
||||||
switchMap(([featureID, objectUrl, ePersonUuid]) => this.authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid)),
|
switchMap(([featureID, objectUrl, ePersonUuid]) => this.authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid)),
|
||||||
returnUnauthorizedUrlTreeOnFalse(this.router)
|
returnForbiddenUrlTreeOrLoginOnFalse(this.router, this.authService, state.url)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import { AuthorizationDataService } from '../authorization-data.service';
|
|||||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have administrator
|
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have administrator
|
||||||
@@ -14,8 +15,8 @@ import { Observable } from 'rxjs/internal/Observable';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class SiteAdministratorGuard extends FeatureAuthorizationGuard {
|
export class SiteAdministratorGuard extends FeatureAuthorizationGuard {
|
||||||
constructor(protected authorizationService: AuthorizationDataService, protected router: Router) {
|
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
|
||||||
super(authorizationService, router);
|
super(authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,6 +5,7 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FeatureID } from '../feature-id';
|
import { FeatureID } from '../feature-id';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have registration
|
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have registration
|
||||||
@@ -14,8 +15,8 @@ import { of as observableOf } from 'rxjs';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class SiteRegisterGuard extends FeatureAuthorizationGuard {
|
export class SiteRegisterGuard extends FeatureAuthorizationGuard {
|
||||||
constructor(protected authorizationService: AuthorizationDataService, protected router: Router) {
|
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
|
||||||
super(authorizationService, router);
|
super(authorizationService, router, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -2,6 +2,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { createSelector, select, Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
|
import { Operation } from 'fast-json-patch/lib/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { filter, map, take, tap } from 'rxjs/operators';
|
import { filter, map, take, tap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
@@ -16,17 +17,24 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { CreateRequest, DeleteRequest, FindListOptions, FindListRequest, PostRequest } from '../data/request.models';
|
import {
|
||||||
|
CreateRequest,
|
||||||
|
DeleteRequest,
|
||||||
|
FindListOptions,
|
||||||
|
FindListRequest,
|
||||||
|
PatchRequest,
|
||||||
|
PostRequest
|
||||||
|
} from '../data/request.models';
|
||||||
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { getResponseFromEntry } from '../shared/operators';
|
import { getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
||||||
import { EPerson } from './models/eperson.model';
|
import { EPerson } from './models/eperson.model';
|
||||||
import { Group } from './models/group.model';
|
import { Group } from './models/group.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
@@ -125,33 +133,51 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to delete a group
|
* Method to delete a group
|
||||||
* @param id The group id to delete
|
* @param group The group to delete
|
||||||
*/
|
*/
|
||||||
public deleteGroup(group: Group): Observable<boolean> {
|
public deleteGroup(group: Group): Observable<[boolean, string]> {
|
||||||
return this.delete(group.id).pipe(map((response: RestResponse) => response.isSuccessful));
|
return this.delete(group.id).pipe(map((response: RestResponse) => {
|
||||||
|
const errorMessage = response.isSuccessful === false ? (response as ErrorResponse).errorMessage : undefined;
|
||||||
|
return [response.isSuccessful, errorMessage];
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or Update a group
|
* Add a new patch to the object cache
|
||||||
* If the group contains an id, it is assumed the eperson already exists and is updated instead
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
* @param group The group to create or update
|
* @param group The group with changes
|
||||||
*/
|
*/
|
||||||
public createOrUpdateGroup(group: Group): Observable<RemoteData<Group>> {
|
updateGroup(group: Group): Observable<RestResponse> {
|
||||||
const isUpdate = hasValue(group.id);
|
const requestId = this.requestService.generateRequestId();
|
||||||
if (isUpdate) {
|
const oldVersion$ = this.findByHref(group._links.self.href);
|
||||||
return this.updateGroup(group);
|
oldVersion$.pipe(
|
||||||
} else {
|
getSucceededRemoteData(),
|
||||||
return this.create(group, null);
|
getRemoteDataPayload(),
|
||||||
|
map((oldGroup: Group) => {
|
||||||
|
const operations = this.generateOperations(oldGroup, group);
|
||||||
|
const patchRequest = new PatchRequest(requestId, group._links.self.href, operations);
|
||||||
|
return this.requestService.configure(patchRequest);
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.fetchResponse(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata operations are generated by the difference between old and new Group
|
||||||
|
* Custom replace operation for the other group Name value
|
||||||
|
* @param oldGroup
|
||||||
|
* @param newGroup
|
||||||
|
*/
|
||||||
|
private generateOperations(oldGroup: Group, newGroup: Group): Operation[] {
|
||||||
|
let operations = this.comparator.diff(oldGroup, newGroup).filter((operation: Operation) => operation.op === 'replace');
|
||||||
|
if (hasValue(oldGroup.name) && oldGroup.name !== newGroup.name) {
|
||||||
|
operations = [...operations, {
|
||||||
|
op: 'replace', path: '/name', value: newGroup.name
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
}
|
return operations;
|
||||||
|
|
||||||
/**
|
|
||||||
* // TODO
|
|
||||||
* @param {DSpaceObject} ePerson The given object
|
|
||||||
*/
|
|
||||||
updateGroup(group: Group): Observable<RemoteData<Group>> {
|
|
||||||
// TODO
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
17
src/app/core/eperson/models/group-dto.model.ts
Normal file
17
src/app/core/eperson/models/group-dto.model.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Group } from './group.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class serves as a Data Transfer Model that contains the Group and whether or not it's able to be deleted
|
||||||
|
*/
|
||||||
|
export class GroupDtoModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Group linked to this object
|
||||||
|
*/
|
||||||
|
public group: Group;
|
||||||
|
/**
|
||||||
|
* Whether or not the linked Group is able to be deleted
|
||||||
|
*/
|
||||||
|
public ableToDelete: boolean;
|
||||||
|
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import { PaginatedList } from '../../data/paginated-list';
|
|||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
|
||||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||||
|
import { DSPACE_OBJECT } from '../../shared/dspace-object.resource-type';
|
||||||
import { HALLink } from '../../shared/hal-link.model';
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
import { EPerson } from './eperson.model';
|
import { EPerson } from './eperson.model';
|
||||||
import { EPERSON } from './eperson.resource-type';
|
import { EPERSON } from './eperson.resource-type';
|
||||||
@@ -41,6 +42,7 @@ export class Group extends DSpaceObject {
|
|||||||
self: HALLink;
|
self: HALLink;
|
||||||
subgroups: HALLink;
|
subgroups: HALLink;
|
||||||
epersons: HALLink;
|
epersons: HALLink;
|
||||||
|
object: HALLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,4 +59,11 @@ export class Group extends DSpaceObject {
|
|||||||
@link(EPERSON, true)
|
@link(EPERSON, true)
|
||||||
public epersons?: Observable<RemoteData<PaginatedList<EPerson>>>;
|
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>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,10 @@ export class ServerResponseService {
|
|||||||
return this.setStatus(401, message)
|
return this.setStatus(401, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setForbidden(message = 'Forbidden'): this {
|
||||||
|
return this.setStatus(403, message)
|
||||||
|
}
|
||||||
|
|
||||||
setNotFound(message = 'Not found'): this {
|
setNotFound(message = 'Not found'): this {
|
||||||
return this.setStatus(404, message)
|
return this.setStatus(404, message)
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
|
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { isEmpty } from '../../shared/empty.util';
|
import { isEmpty } from '../../shared/empty.util';
|
||||||
import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
|
|
||||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -105,9 +104,9 @@ export class Item extends DSpaceObject implements ChildHALResource {
|
|||||||
* Method that returns as which type of object this object should be rendered
|
* Method that returns as which type of object this object should be rendered
|
||||||
*/
|
*/
|
||||||
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
||||||
let entityType = this.firstMetadataValue('relationship.type');
|
const entityType = this.firstMetadataValue('relationship.type');
|
||||||
if (isEmpty(entityType)) {
|
if (isEmpty(entityType)) {
|
||||||
entityType = DEFAULT_ENTITY_TYPE;
|
return super.getRenderTypes();
|
||||||
}
|
}
|
||||||
return [entityType, ...super.getRenderTypes()];
|
return [entityType, ...super.getRenderTypes()];
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ import {
|
|||||||
getResourceLinksFromResponse,
|
getResourceLinksFromResponse,
|
||||||
getResponseFromEntry,
|
getResponseFromEntry,
|
||||||
getSucceededRemoteData,
|
getSucceededRemoteData,
|
||||||
redirectOn404Or401
|
redirectOn4xx
|
||||||
} from './operators';
|
} from './operators';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { RemoteDataError } from '../data/remote-data-error';
|
import { RemoteDataError } from '../data/remote-data-error';
|
||||||
@@ -200,39 +200,67 @@ describe('Core Module - RxJS Operators', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('redirectOn404Or401', () => {
|
describe('redirectOn4xx', () => {
|
||||||
let router;
|
let router;
|
||||||
|
let authService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
router = jasmine.createSpyObj('router', ['navigateByUrl']);
|
router = jasmine.createSpyObj('router', ['navigateByUrl']);
|
||||||
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true),
|
||||||
|
setRedirectUrl: {}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => {
|
it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => {
|
||||||
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found'));
|
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found'));
|
||||||
|
|
||||||
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true });
|
expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call navigateByUrl to a 401 page, when the remote data contains a 401 error', () => {
|
it('should call navigateByUrl to a 403 page, when the remote data contains a 403 error', () => {
|
||||||
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized'));
|
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(403, 'Forbidden', 'Forbidden access'));
|
||||||
|
|
||||||
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/401', { skipLocationChange: true });
|
expect(router.navigateByUrl).toHaveBeenCalledWith('/403', { skipLocationChange: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains another error than a 404 or 401', () => {
|
it('should not call navigateByUrl to a 404, 403 or 401 page, when the remote data contains another error than a 404, 403 or 401', () => {
|
||||||
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong'));
|
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong'));
|
||||||
|
|
||||||
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains no error', () => {
|
it('should not call navigateByUrl to a 404, 403 or 401 page, when the remote data contains no error', () => {
|
||||||
const testRD = createSuccessfulRemoteDataObject(undefined);
|
const testRD = createSuccessfulRemoteDataObject(undefined);
|
||||||
|
|
||||||
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when the user is not authenticated', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the redirect url and navigate to login when the remote data contains a 401 error', () => {
|
||||||
|
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized'));
|
||||||
|
|
||||||
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
|
expect(authService.setRedirectUrl).toHaveBeenCalled();
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalledWith('login');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the redirect url and navigate to login when the remote data contains a 403 error', () => {
|
||||||
|
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(403, 'Forbidden', 'Forbidden access'));
|
||||||
|
|
||||||
|
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
|
||||||
|
expect(authService.setRedirectUrl).toHaveBeenCalled();
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalledWith('login');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getResponseFromEntry', () => {
|
describe('getResponseFromEntry', () => {
|
||||||
|
@@ -13,8 +13,9 @@ import { MetadataField } from '../metadata/metadata-field.model';
|
|||||||
import { MetadataSchema } from '../metadata/metadata-schema.model';
|
import { MetadataSchema } from '../metadata/metadata-schema.model';
|
||||||
import { BrowseDefinition } from './browse-definition.model';
|
import { BrowseDefinition } from './browse-definition.model';
|
||||||
import { DSpaceObject } from './dspace-object.model';
|
import { DSpaceObject } from './dspace-object.model';
|
||||||
import { getPageNotFoundRoute, getUnauthorizedRoute } from '../../app-routing-paths';
|
import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths';
|
||||||
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
|
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
|
||||||
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains custom RxJS operators that can be used in multiple places
|
* This file contains custom RxJS operators that can be used in multiple places
|
||||||
@@ -75,6 +76,10 @@ export const getSucceededRemoteWithNotEmptyData = () =>
|
|||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
||||||
|
|
||||||
|
export const getSucceededRemoteWithNotEmptyDataOrFailed = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
|
source.pipe(find((rd: RemoteData<T>) => (rd.hasSucceeded && isNotEmpty(rd.payload)) || rd.hasFailed));
|
||||||
|
|
||||||
export const getSucceededOrNoContentResponse = () =>
|
export const getSucceededOrNoContentResponse = () =>
|
||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded || rd.hasNoContent));
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded || rd.hasNoContent));
|
||||||
@@ -178,29 +183,47 @@ export const getAllSucceededRemoteListPayload = () =>
|
|||||||
* Operator that checks if a remote data object returned a 401 or 404 error
|
* Operator that checks if a remote data object returned a 401 or 404 error
|
||||||
* When it does contain such an error, it will redirect the user to the related error page, without altering the current URL
|
* When it does contain such an error, it will redirect the user to the related error page, without altering the current URL
|
||||||
* @param router The router used to navigate to a new page
|
* @param router The router used to navigate to a new page
|
||||||
|
* @param authService Service to check if the user is authenticated
|
||||||
*/
|
*/
|
||||||
export const redirectOn404Or401 = (router: Router) =>
|
export const redirectOn4xx = (router: Router, authService: AuthService) =>
|
||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(
|
observableCombineLatest(source, authService.isAuthenticated()).pipe(
|
||||||
tap((rd: RemoteData<T>) => {
|
map(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => {
|
||||||
if (rd.hasFailed) {
|
if (rd.hasFailed) {
|
||||||
if (rd.error.statusCode === 404) {
|
if (rd.error.statusCode === 404) {
|
||||||
router.navigateByUrl(getPageNotFoundRoute(), {skipLocationChange: true});
|
router.navigateByUrl(getPageNotFoundRoute(), {skipLocationChange: true});
|
||||||
} else if (rd.error.statusCode === 401) {
|
} else if (rd.error.statusCode === 403 || rd.error.statusCode === 401) {
|
||||||
router.navigateByUrl(getUnauthorizedRoute(), {skipLocationChange: true});
|
if (isAuthenticated) {
|
||||||
|
router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true});
|
||||||
|
} else {
|
||||||
|
authService.setRedirectUrl(router.url);
|
||||||
|
router.navigateByUrl('login');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return rd;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operator that returns a UrlTree to the unauthorized page when the boolean received is false
|
* Operator that returns a UrlTree to a forbidden page or the login page when the boolean received is false
|
||||||
* @param router
|
* @param router The router used to navigate to a forbidden page
|
||||||
|
* @param authService The AuthService used to determine whether or not the user is logged in
|
||||||
|
* @param redirectUrl The URL to redirect back to after logging in
|
||||||
*/
|
*/
|
||||||
export const returnUnauthorizedUrlTreeOnFalse = (router: Router) =>
|
export const returnForbiddenUrlTreeOrLoginOnFalse = (router: Router, authService: AuthService, redirectUrl: string) =>
|
||||||
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
|
(source: Observable<boolean>): Observable<boolean | UrlTree> =>
|
||||||
source.pipe(
|
observableCombineLatest(source, authService.isAuthenticated()).pipe(
|
||||||
map((authorized: boolean) => {
|
map(([authorized, authenticated]: [boolean, boolean]) => {
|
||||||
return authorized ? authorized : router.parseUrl(getUnauthorizedRoute())
|
if (authorized) {
|
||||||
|
return authorized;
|
||||||
|
} else {
|
||||||
|
if (authenticated) {
|
||||||
|
return router.parseUrl(getForbiddenRoute());
|
||||||
|
} else {
|
||||||
|
authService.setRedirectUrl(redirectUrl);
|
||||||
|
return router.parseUrl('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
|
||||||
import { JournalIssueSearchResultGridElementComponent } from './journal-issue-search-result-grid-element.component';
|
import { JournalIssueSearchResultGridElementComponent } from './journal-issue-search-result-grid-element.component';
|
||||||
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
|
||||||
import { JournalVolumeSearchResultGridElementComponent } from './journal-volume-search-result-grid-element.component';
|
import { JournalVolumeSearchResultGridElementComponent } from './journal-volume-search-result-grid-element.component';
|
||||||
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
|
||||||
import { JournalSearchResultGridElementComponent } from './journal-search-result-grid-element.component';
|
import { JournalSearchResultGridElementComponent } from './journal-search-result-grid-element.component';
|
||||||
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('organization.legalName')"></h4>
|
<h4 class="card-title" [innerHTML]="firstMetadataValue('organization.legalName')"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -4,7 +4,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
|
|||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { OrgUnitSearchResultGridElementComponent } from './org-unit-search-result-grid-element.component';
|
import { OrgUnitSearchResultGridElementComponent } from './org-unit-search-result-grid-element.component';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title"
|
<h4 class="card-title"
|
||||||
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>
|
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>
|
||||||
|
@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
|
||||||
import { PersonSearchResultGridElementComponent } from './person-search-result-grid-element.component';
|
import { PersonSearchResultGridElementComponent } from './person-search-result-grid-element.component';
|
||||||
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
|
||||||
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -4,7 +4,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
|
|||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
import { ProjectSearchResultGridElementComponent } from './project-search-result-grid-element.component';
|
import { ProjectSearchResultGridElementComponent } from './project-search-result-grid-element.component';
|
||||||
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
|
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
|
||||||
|
|
||||||
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
|
||||||
mockItemWithMetadata.hitHighlights = {};
|
mockItemWithMetadata.hitHighlights = {};
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
||||||
|
10
src/app/forbidden/forbidden.component.html
Normal file
10
src/app/forbidden/forbidden.component.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="forbidden container">
|
||||||
|
<h1>403</h1>
|
||||||
|
<h2><small>{{"403.forbidden" | translate}}</small></h2>
|
||||||
|
<br/>
|
||||||
|
<p>{{"403.help" | translate}}</p>
|
||||||
|
<br/>
|
||||||
|
<p class="text-center">
|
||||||
|
<a routerLink="/home" class="btn btn-primary">{{"403.link.home-page" | translate}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
@@ -3,30 +3,30 @@ import { AuthService } from '../core/auth/auth.service';
|
|||||||
import { ServerResponseService } from '../core/services/server-response.service';
|
import { ServerResponseService } from '../core/services/server-response.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component representing the `Unauthorized` DSpace page.
|
* This component representing the `Forbidden` DSpace page.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-unauthorized',
|
selector: 'ds-forbidden',
|
||||||
templateUrl: './unauthorized.component.html',
|
templateUrl: './forbidden.component.html',
|
||||||
styleUrls: ['./unauthorized.component.scss']
|
styleUrls: ['./forbidden.component.scss']
|
||||||
})
|
})
|
||||||
export class UnauthorizedComponent implements OnInit {
|
export class ForbiddenComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
*
|
*
|
||||||
* @param {AuthService} authservice
|
* @param {AuthService} authService
|
||||||
* @param {ServerResponseService} responseService
|
* @param {ServerResponseService} responseService
|
||||||
*/
|
*/
|
||||||
constructor(private authservice: AuthService, private responseService: ServerResponseService) {
|
constructor(private authService: AuthService, private responseService: ServerResponseService) {
|
||||||
this.responseService.setUnauthorized();
|
this.responseService.setForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove redirect url from the state
|
* Remove redirect url from the state
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.authservice.clearRedirectUrl();
|
this.authService.clearRedirectUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -12,7 +12,7 @@ import { ProcessDataService } from '../../core/data/processes/process-data.servi
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { getFirstSucceededRemoteDataPayload, redirectOn404Or401 } from '../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { AlertType } from '../../shared/alert/aletr-type';
|
import { AlertType } from '../../shared/alert/aletr-type';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
@@ -84,7 +84,7 @@ export class ProcessDetailComponent implements OnInit {
|
|||||||
map((data) => {
|
map((data) => {
|
||||||
return data.process as RemoteData<Process>
|
return data.process as RemoteData<Process>
|
||||||
}),
|
}),
|
||||||
redirectOn404Or401(this.router)
|
redirectOn4xx(this.router, this.authService)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.filesRD$ = this.processRD$.pipe(
|
this.filesRD$ = this.processRD$.pipe(
|
||||||
|
@@ -12,4 +12,11 @@ export class AuthServiceMock {
|
|||||||
public getShortlivedToken(): Observable<string> {
|
public getShortlivedToken(): Observable<string> {
|
||||||
return observableOf('token');
|
return observableOf('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isAuthenticated(): Observable<boolean> {
|
||||||
|
return observableOf(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setRedirectUrl(url: string) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor'
|
|||||||
import { Context } from '../../../../core/shared/context.model';
|
import { Context } from '../../../../core/shared/context.model';
|
||||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
import * as listableObjectDecorators from './listable-object.decorator';
|
import * as listableObjectDecorators from './listable-object.decorator';
|
||||||
import { PublicationListElementComponent } from '../../../object-list/item-list-element/item-types/publication/publication-list-element.component';
|
import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
|
||||||
import { ListableObjectDirective } from './listable-object.directive';
|
import { ListableObjectDirective } from './listable-object.directive';
|
||||||
import { spyOnExported } from '../../../testing/utils.test';
|
import { spyOnExported } from '../../../testing/utils.test';
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [],
|
||||||
declarations: [ListableObjectComponentLoaderComponent, PublicationListElementComponent, ListableObjectDirective],
|
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
providers: [ComponentFactoryResolver]
|
providers: [ComponentFactoryResolver]
|
||||||
}).overrideComponent(ListableObjectComponentLoaderComponent, {
|
}).overrideComponent(ListableObjectComponentLoaderComponent, {
|
||||||
set: {
|
set: {
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
entryComponents: [PublicationListElementComponent]
|
entryComponents: [ItemListElementComponent]
|
||||||
}
|
}
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -45,7 +45,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
|||||||
comp.object = new TestType();
|
comp.object = new TestType();
|
||||||
comp.viewMode = testViewMode;
|
comp.viewMode = testViewMode;
|
||||||
comp.context = testContext;
|
comp.context = testContext;
|
||||||
spyOnExported(listableObjectDecorators, 'getListableObjectComponent').and.returnValue(PublicationListElementComponent);
|
spyOnExported(listableObjectDecorators, 'getListableObjectComponent').and.returnValue(ItemListElementComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
<ds-item-search-result-grid-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</ds-item-search-result-grid-element>
|
@@ -4,7 +4,7 @@ import { TruncatePipe } from '../../../../utils/truncate.pipe';
|
|||||||
import { TruncatableService } from '../../../../truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../truncatable/truncatable.service';
|
||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { PublicationGridElementComponent } from './publication-grid-element.component';
|
import { ItemGridElementComponent } from './item-grid-element.component';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
|
||||||
@@ -41,7 +41,7 @@ const mockItem = Object.assign(new Item(), {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PublicationGridElementComponent', () => {
|
describe('ItemGridElementComponent', () => {
|
||||||
let comp;
|
let comp;
|
||||||
let fixture;
|
let fixture;
|
||||||
|
|
||||||
@@ -52,18 +52,18 @@ describe('PublicationGridElementComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [NoopAnimationsModule],
|
imports: [NoopAnimationsModule],
|
||||||
declarations: [PublicationGridElementComponent, TruncatePipe],
|
declarations: [ItemGridElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(PublicationGridElementComponent, {
|
}).overrideComponent(ItemGridElementComponent, {
|
||||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
fixture = TestBed.createComponent(PublicationGridElementComponent);
|
fixture = TestBed.createComponent(ItemGridElementComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ describe('PublicationGridElementComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it(`should contain a PublicationGridElementComponent`, () => {
|
it(`should contain a PublicationGridElementComponent`, () => {
|
||||||
const publicationGridElement = fixture.debugElement.query(By.css(`ds-publication-search-result-grid-element`));
|
const publicationGridElement = fixture.debugElement.query(By.css(`ds-item-search-result-grid-element`));
|
||||||
expect(publicationGridElement).not.toBeNull();
|
expect(publicationGridElement).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
@@ -8,13 +8,13 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
@listableObjectComponent('Publication', ViewMode.GridElement)
|
@listableObjectComponent('Publication', ViewMode.GridElement)
|
||||||
@listableObjectComponent(Item, ViewMode.GridElement)
|
@listableObjectComponent(Item, ViewMode.GridElement)
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-publication-grid-element',
|
selector: 'ds-item-grid-element',
|
||||||
styleUrls: ['./publication-grid-element.component.scss'],
|
styleUrls: ['./item-grid-element.component.scss'],
|
||||||
templateUrl: './publication-grid-element.component.html',
|
templateUrl: './item-grid-element.component.html',
|
||||||
animations: [focusShadow]
|
animations: [focusShadow]
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying a grid element for an item of the type Publication
|
* The component for displaying a grid element for an item of the type Publication
|
||||||
*/
|
*/
|
||||||
export class PublicationGridElementComponent extends AbstractListableElementComponent<Item> {
|
export class ItemGridElementComponent extends AbstractListableElementComponent<Item> {
|
||||||
}
|
}
|
@@ -1,4 +0,0 @@
|
|||||||
<ds-publication-search-result-grid-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType">
|
|
||||||
<ng-content></ng-content>
|
|
||||||
<ng-content></ng-content>
|
|
||||||
</ds-publication-search-result-grid-element>
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user