mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into DSC-389
This commit is contained in:
@@ -359,7 +359,7 @@ dspace-angular
|
|||||||
│ ├── plugins * Folder for Cypress plugins (if any)
|
│ ├── plugins * Folder for Cypress plugins (if any)
|
||||||
│ ├── support * Folder for global e2e test actions/commands (run for all tests)
|
│ ├── support * Folder for global e2e test actions/commands (run for all tests)
|
||||||
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
||||||
├── docker *
|
├── docker * See docker/README.md for details
|
||||||
│ ├── cli.assetstore.yml *
|
│ ├── cli.assetstore.yml *
|
||||||
│ ├── cli.ingest.yml *
|
│ ├── cli.ingest.yml *
|
||||||
│ ├── cli.yml *
|
│ ├── cli.yml *
|
||||||
@@ -367,8 +367,6 @@ dspace-angular
|
|||||||
│ ├── docker-compose-ci.yml *
|
│ ├── docker-compose-ci.yml *
|
||||||
│ ├── docker-compose-rest.yml *
|
│ ├── docker-compose-rest.yml *
|
||||||
│ ├── docker-compose.yml *
|
│ ├── docker-compose.yml *
|
||||||
│ ├── environment.dev.ts *
|
|
||||||
│ ├── local.cfg *
|
|
||||||
│ └── README.md *
|
│ └── README.md *
|
||||||
├── docs * Folder for documentation
|
├── docs * Folder for documentation
|
||||||
│ └── Configuration.md * Configuration documentation
|
│ └── Configuration.md * Configuration documentation
|
||||||
|
@@ -53,7 +53,7 @@ describe('Search Page', () => {
|
|||||||
|
|
||||||
// Click to display grid view
|
// Click to display grid view
|
||||||
// TODO: These buttons should likely have an easier way to uniquely select
|
// TODO: These buttons should likely have an easier way to uniquely select
|
||||||
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?spc.sf=score&spc.sd=DESC&view=grid"] > .fas').click();
|
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?view=grid"] > .fas').click();
|
||||||
|
|
||||||
// <ds-search-page> tag must be loaded
|
// <ds-search-page> tag must be loaded
|
||||||
cy.get('ds-search-page').should('exist');
|
cy.get('ds-search-page').should('exist');
|
||||||
|
@@ -29,10 +29,6 @@ docker push dspace/dspace-angular:dspace-7_x
|
|||||||
- 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.
|
||||||
- cli.assetstore.yml
|
- cli.assetstore.yml
|
||||||
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
|
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
|
||||||
- environment.dev.ts
|
|
||||||
- Environment file for running DSpace Angular in Docker
|
|
||||||
- local.cfg
|
|
||||||
- Environment file for running the DSpace 7 REST API in Docker.
|
|
||||||
|
|
||||||
|
|
||||||
## To refresh / pull DSpace images from Dockerhub
|
## To refresh / pull DSpace images from Dockerhub
|
||||||
|
@@ -18,10 +18,19 @@ services:
|
|||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
||||||
container_name: dspace-cli
|
container_name: dspace-cli
|
||||||
#environment:
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
volumes:
|
volumes:
|
||||||
- "assetstore:/dspace/assetstore"
|
- "assetstore:/dspace/assetstore"
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
entrypoint: /dspace/bin/dspace
|
entrypoint: /dspace/bin/dspace
|
||||||
command: help
|
command: help
|
||||||
networks:
|
networks:
|
||||||
|
@@ -17,6 +17,19 @@ services:
|
|||||||
# DSpace (backend) webapp container
|
# DSpace (backend) webapp container
|
||||||
dspace:
|
dspace:
|
||||||
container_name: dspace
|
container_name: dspace
|
||||||
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir, dspace.server.url and dspace.ui.url
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
dspace__P__server__P__url: http://localhost:8080/server
|
||||||
|
dspace__P__ui__P__url: http://localhost:4000
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:dspace-7_x-test
|
image: dspace/dspace:dspace-7_x-test
|
||||||
@@ -29,7 +42,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||||
- solr_configs:/dspace/solr
|
- solr_configs:/dspace/solr
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
|
@@ -13,10 +13,32 @@
|
|||||||
version: '3.7'
|
version: '3.7'
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
# Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container.
|
||||||
|
# If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below.
|
||||||
|
- subnet: 172.23.0.0/16
|
||||||
services:
|
services:
|
||||||
# DSpace (backend) webapp container
|
# DSpace (backend) webapp container
|
||||||
dspace:
|
dspace:
|
||||||
container_name: dspace
|
container_name: dspace
|
||||||
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
dspace__P__server__P__url: http://localhost:8080/server
|
||||||
|
dspace__P__ui__P__url: http://localhost:4000
|
||||||
|
dspace__P__name: 'DSpace Started with Docker Compose'
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
|
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||||
|
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||||
|
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||||
image: dspace/dspace:dspace-7_x-test
|
image: dspace/dspace:dspace-7_x-test
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
@@ -29,7 +51,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||||
- solr_configs:/dspace/solr
|
- solr_configs:/dspace/solr
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
|
@@ -16,10 +16,14 @@ services:
|
|||||||
dspace-angular:
|
dspace-angular:
|
||||||
container_name: dspace-angular
|
container_name: dspace-angular
|
||||||
environment:
|
environment:
|
||||||
DSPACE_HOST: dspace-angular
|
DSPACE_UI_SSL: 'false'
|
||||||
DSPACE_NAMESPACE: /
|
DSPACE_UI_HOST: dspace-angular
|
||||||
DSPACE_PORT: '4000'
|
DSPACE_UI_PORT: '4000'
|
||||||
DSPACE_SSL: "false"
|
DSPACE_UI_NAMESPACE: /
|
||||||
|
DSPACE_REST_SSL: 'false'
|
||||||
|
DSPACE_REST_HOST: localhost
|
||||||
|
DSPACE_REST_PORT: 8080
|
||||||
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: dspace/dspace-angular:dspace-7_x
|
image: dspace/dspace-angular:dspace-7_x
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
@@ -33,5 +37,3 @@ services:
|
|||||||
target: 9876
|
target: 9876
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
|
||||||
- ./environment.dev.ts:/app/src/environments/environment.dev.ts
|
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 file is based on environment.template.ts provided by Angular UI
|
|
||||||
export const environment = {
|
|
||||||
// Default to using the local REST API (running in Docker)
|
|
||||||
rest: {
|
|
||||||
ssl: false,
|
|
||||||
host: 'localhost',
|
|
||||||
port: 8080,
|
|
||||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
|
||||||
nameSpace: '/server'
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,6 +0,0 @@
|
|||||||
dspace.dir=/dspace
|
|
||||||
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
|
||||||
dspace.server.url=http://localhost:8080/server
|
|
||||||
dspace.ui.url=http://localhost:4000
|
|
||||||
dspace.name=DSpace Started with Docker Compose
|
|
||||||
solr.server=http://dspacesolr:8983/solr
|
|
@@ -30,7 +30,7 @@
|
|||||||
"clean:json": "rimraf *.records.json",
|
"clean:json": "rimraf *.records.json",
|
||||||
"clean:node": "rimraf node_modules",
|
"clean:node": "rimraf node_modules",
|
||||||
"clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json",
|
"clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json",
|
||||||
"clean": "yarn run clean:prod && yarn run clean:node && yarn run clean:dev:config",
|
"clean": "yarn run clean:prod && yarn run clean:dev:config && yarn run clean:node",
|
||||||
"sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
"sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
||||||
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
||||||
"merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
"merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
||||||
|
@@ -9,13 +9,15 @@ import { GroupFormComponent } from './group-registry/group-form/group-form.compo
|
|||||||
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
||||||
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
||||||
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
||||||
|
import { FormModule } from '../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
AccessControlRoutingModule
|
AccessControlRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EPeopleRegistryComponent,
|
EPeopleRegistryComponent,
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div between class="btn-group">
|
<div between class="btn-group">
|
||||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)">
|
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
|
||||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,9 +36,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
|
<ds-loading [showMessage]="false" *ngIf="!formGroup"></ds-loading>
|
||||||
|
|
||||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||||
|
|
||||||
|
<ds-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-loading>
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
|
@@ -2,7 +2,7 @@ import { Observable, of as observableOf } from 'rxjs';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
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';
|
||||||
@@ -14,6 +14,7 @@ import { EPerson } from '../../../core/eperson/models/eperson.model';
|
|||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
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 { EPeopleRegistryComponent } from '../epeople-registry.component';
|
||||||
import { EPersonFormComponent } from './eperson-form.component';
|
import { EPersonFormComponent } from './eperson-form.component';
|
||||||
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
|
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
@@ -28,9 +29,8 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
|
|||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
|
|
||||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||||
|
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||||
|
|
||||||
describe('EPersonFormComponent', () => {
|
describe('EPersonFormComponent', () => {
|
||||||
let component: EPersonFormComponent;
|
let component: EPersonFormComponent;
|
||||||
@@ -42,6 +42,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
let authService: AuthServiceStub;
|
let authService: AuthServiceStub;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let groupsDataService: GroupDataService;
|
let groupsDataService: GroupDataService;
|
||||||
|
let epersonRegistrationService: EpersonRegistrationService;
|
||||||
|
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
@@ -199,12 +200,18 @@ describe('EPersonFormComponent', () => {
|
|||||||
{ provide: AuthService, useValue: authService },
|
{ provide: AuthService, useValue: authService },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }
|
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])},
|
||||||
|
{ provide: EpersonRegistrationService, useValue: epersonRegistrationService },
|
||||||
|
EPeopleRegistryComponent
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
||||||
|
registerEmail: createSuccessfulRemoteDataObject$(null)
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(EPersonFormComponent);
|
fixture = TestBed.createComponent(EPersonFormComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
@@ -514,4 +521,23 @@ describe('EPersonFormComponent', () => {
|
|||||||
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);
|
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Reset Password', () => {
|
||||||
|
let ePersonId;
|
||||||
|
let ePersonEmail;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ePersonId = 'testEPersonId';
|
||||||
|
ePersonEmail = 'person.email@4science.it';
|
||||||
|
component.epersonInitial = Object.assign(new EPerson(), {
|
||||||
|
id: ePersonId,
|
||||||
|
email: ePersonEmail
|
||||||
|
});
|
||||||
|
component.resetPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call epersonRegistrationService.registerEmail', () => {
|
||||||
|
expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -34,6 +34,8 @@ import { NoContent } from '../../../core/shared/NoContent.model';
|
|||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||||
|
import { Registration } from '../../../core/shared/registration.model';
|
||||||
|
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-eperson-form',
|
selector: 'ds-eperson-form',
|
||||||
@@ -121,7 +123,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* Observable whether or not the admin is allowed to reset the EPerson's password
|
* Observable whether or not the admin is allowed to reset the EPerson's password
|
||||||
* TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
|
* TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
|
||||||
*/
|
*/
|
||||||
canReset$: Observable<boolean> = observableOf(false);
|
canReset$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable whether or not the admin is allowed to delete the EPerson
|
* Observable whether or not the admin is allowed to delete the EPerson
|
||||||
@@ -167,17 +169,20 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
emailValueChangeSubscribe: Subscription;
|
emailValueChangeSubscribe: Subscription;
|
||||||
|
|
||||||
constructor(protected changeDetectorRef: ChangeDetectorRef,
|
constructor(
|
||||||
public epersonService: EPersonDataService,
|
protected changeDetectorRef: ChangeDetectorRef,
|
||||||
public groupsDataService: GroupDataService,
|
public epersonService: EPersonDataService,
|
||||||
private formBuilderService: FormBuilderService,
|
public groupsDataService: GroupDataService,
|
||||||
private translateService: TranslateService,
|
private formBuilderService: FormBuilderService,
|
||||||
private notificationsService: NotificationsService,
|
private translateService: TranslateService,
|
||||||
private authService: AuthService,
|
private notificationsService: NotificationsService,
|
||||||
private authorizationService: AuthorizationDataService,
|
private authService: AuthService,
|
||||||
private modalService: NgbModal,
|
private authorizationService: AuthorizationDataService,
|
||||||
private paginationService: PaginationService,
|
private modalService: NgbModal,
|
||||||
public requestService: RequestService) {
|
private paginationService: PaginationService,
|
||||||
|
public requestService: RequestService,
|
||||||
|
private epersonRegistrationService: EpersonRegistrationService,
|
||||||
|
) {
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
this.epersonInitial = eperson;
|
this.epersonInitial = eperson;
|
||||||
if (hasValue(eperson)) {
|
if (hasValue(eperson)) {
|
||||||
@@ -310,6 +315,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.canDelete$ = activeEPerson$.pipe(
|
this.canDelete$ = activeEPerson$.pipe(
|
||||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
||||||
);
|
);
|
||||||
|
this.canReset$ = observableOf(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +485,26 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.isImpersonated = false;
|
this.isImpersonated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an email to current eperson address with the information
|
||||||
|
* to reset password
|
||||||
|
*/
|
||||||
|
resetPassword() {
|
||||||
|
if (hasValue(this.epersonInitial.email)) {
|
||||||
|
this.epersonRegistrationService.registerEmail(this.epersonInitial.email).pipe(getFirstCompletedRemoteData())
|
||||||
|
.subscribe((response: RemoteData<Registration>) => {
|
||||||
|
if (response.hasSucceeded) {
|
||||||
|
this.notificationsService.success(this.translateService.get('admin.access-control.epeople.actions.reset'),
|
||||||
|
this.translateService.get('forgot-email.form.success.content', {email: this.epersonInitial.email}));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get('forgot-email.form.error.head'),
|
||||||
|
this.translateService.get('forgot-email.form.error.content', {email: this.epersonInitial.email}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
*/
|
*/
|
||||||
|
@@ -8,6 +8,7 @@ import { SharedModule } from '../../shared/shared.module';
|
|||||||
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
|
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
|
||||||
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
|
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
|
||||||
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
|
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -15,7 +16,8 @@ import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.mo
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
BitstreamFormatsModule,
|
BitstreamFormatsModule,
|
||||||
AdminRegistriesRoutingModule
|
AdminRegistriesRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MetadataRegistryComponent,
|
MetadataRegistryComponent,
|
||||||
|
@@ -7,13 +7,15 @@ import { FormatFormComponent } from './format-form/format-form.component';
|
|||||||
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
||||||
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
|
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
|
||||||
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
||||||
|
import { FormModule } from '../../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
BitstreamFormatsRoutingModule
|
BitstreamFormatsRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BitstreamFormatsComponent,
|
BitstreamFormatsComponent,
|
||||||
|
@@ -10,6 +10,7 @@ import { CollectionAdminSearchResultGridElementComponent } from './admin-search-
|
|||||||
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
||||||
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -24,6 +25,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
SearchModule,
|
||||||
SharedModule.withEntryComponents(),
|
SharedModule.withEntryComponents(),
|
||||||
JournalEntitiesModule.withEntryComponents(),
|
JournalEntitiesModule.withEntryComponents(),
|
||||||
ResearchEntitiesModule.withEntryComponents()
|
ResearchEntitiesModule.withEntryComponents()
|
||||||
|
@@ -18,6 +18,8 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
import createSpy = jasmine.createSpy;
|
import createSpy = jasmine.createSpy;
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
describe('AdminSidebarComponent', () => {
|
describe('AdminSidebarComponent', () => {
|
||||||
let comp: AdminSidebarComponent;
|
let comp: AdminSidebarComponent;
|
||||||
@@ -26,6 +28,28 @@ describe('AdminSidebarComponent', () => {
|
|||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let scriptService;
|
let scriptService;
|
||||||
|
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
uuid: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://localhost:8000/items/fake-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
dso: createSuccessfulRemoteDataObject(mockItem)
|
||||||
|
}),
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true)
|
isAuthorized: observableOf(true)
|
||||||
@@ -42,6 +66,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
{ provide: ActivatedRoute, useValue: {} },
|
{ provide: ActivatedRoute, useValue: {} },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: ScriptDataService, useValue: scriptService },
|
{ provide: ScriptDataService, useValue: scriptService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
{
|
{
|
||||||
provide: NgbModal, useValue: {
|
provide: NgbModal, useValue: {
|
||||||
open: () => {/*comment*/
|
open: () => {/*comment*/
|
||||||
@@ -229,19 +254,19 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
it('should contain site admin section', () => {
|
it('should contain site admin section', () => {
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'admin_search', visible: true,
|
id: 'admin_search', visible: true,
|
||||||
}));
|
}));
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'registries', visible: true,
|
id: 'registries', visible: true,
|
||||||
}));
|
}));
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
parentID: 'registries', visible: true,
|
parentID: 'registries', visible: true,
|
||||||
}));
|
}));
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'curation_tasks', visible: true,
|
id: 'curation_tasks', visible: true,
|
||||||
}));
|
}));
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'workflow', visible: true,
|
id: 'workflow', visible: true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -259,7 +284,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
it('should show edit_community', () => {
|
it('should show edit_community', () => {
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'edit_community', visible: true,
|
id: 'edit_community', visible: true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -277,7 +302,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
it('should show edit_collection', () => {
|
it('should show edit_collection', () => {
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'edit_collection', visible: true,
|
id: 'edit_collection', visible: true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -295,10 +320,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
|
|
||||||
it('should show access control section', () => {
|
it('should show access control section', () => {
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
id: 'access_control', visible: true,
|
id: 'access_control', visible: true,
|
||||||
}));
|
}));
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
|
||||||
parentID: 'access_control', visible: true,
|
parentID: 'access_control', visible: true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -21,6 +21,7 @@ import { MenuService } from '../../shared/menu/menu.service';
|
|||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the admin sidebar
|
* Component representing the admin sidebar
|
||||||
@@ -63,14 +64,15 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
inFocus$: BehaviorSubject<boolean>;
|
inFocus$: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
constructor(protected menuService: MenuService,
|
constructor(protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
private variableService: CSSVariableService,
|
private variableService: CSSVariableService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private authorizationService: AuthorizationDataService,
|
public authorizationService: AuthorizationDataService,
|
||||||
private scriptDataService: ScriptDataService,
|
private scriptDataService: ScriptDataService,
|
||||||
|
public route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
super(menuService, injector);
|
super(menuService, injector, authorizationService, route);
|
||||||
this.inFocus$ = new BehaviorSubject(false);
|
this.inFocus$ = new BehaviorSubject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
type: MenuItemType.TEXT,
|
type: MenuItemType.TEXT,
|
||||||
text: 'menu.section.new'
|
text: 'menu.section.new'
|
||||||
} as TextMenuItemModel,
|
} as TextMenuItemModel,
|
||||||
icon: 'plus',
|
icon: 'plus',
|
||||||
index: 0
|
index: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -5,6 +5,7 @@ import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from './adm
|
|||||||
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
||||||
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -14,6 +15,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
SearchModule,
|
||||||
SharedModule.withEntryComponents()
|
SharedModule.withEntryComponents()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -24,7 +24,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
AccessControlModule,
|
AccessControlModule,
|
||||||
AdminSearchModule.withEntryComponents(),
|
AdminSearchModule.withEntryComponents(),
|
||||||
AdminWorkflowModuleModule.withEntryComponents(),
|
AdminWorkflowModuleModule.withEntryComponents(),
|
||||||
SharedModule,
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AdminCurationTasksComponent,
|
AdminCurationTasksComponent,
|
||||||
|
@@ -89,6 +89,12 @@ export function getPageNotFoundRoute() {
|
|||||||
return `/${PAGE_NOT_FOUND_PATH}`;
|
return `/${PAGE_NOT_FOUND_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const INTERNAL_SERVER_ERROR = '500';
|
||||||
|
|
||||||
|
export function getPageInternalServerErrorRoute() {
|
||||||
|
return `/${INTERNAL_SERVER_ERROR}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const INFO_MODULE_PATH = 'info';
|
export const INFO_MODULE_PATH = 'info';
|
||||||
export function getInfoModulePath() {
|
export function getInfoModulePath() {
|
||||||
return `/${INFO_MODULE_PATH}`;
|
return `/${INFO_MODULE_PATH}`;
|
||||||
|
@@ -11,10 +11,12 @@ import {
|
|||||||
FORBIDDEN_PATH,
|
FORBIDDEN_PATH,
|
||||||
FORGOT_PASSWORD_PATH,
|
FORGOT_PASSWORD_PATH,
|
||||||
INFO_MODULE_PATH,
|
INFO_MODULE_PATH,
|
||||||
|
INTERNAL_SERVER_ERROR,
|
||||||
|
LEGACY_BITSTREAM_MODULE_PATH,
|
||||||
PROFILE_MODULE_PATH,
|
PROFILE_MODULE_PATH,
|
||||||
REGISTER_PATH,
|
REGISTER_PATH,
|
||||||
|
REQUEST_COPY_MODULE_PATH,
|
||||||
WORKFLOW_ITEM_MODULE_PATH,
|
WORKFLOW_ITEM_MODULE_PATH,
|
||||||
LEGACY_BITSTREAM_MODULE_PATH, REQUEST_COPY_MODULE_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';
|
||||||
@@ -26,14 +28,25 @@ import { SiteRegisterGuard } from './core/data/feature-authorization/feature-aut
|
|||||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||||
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||||
|
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||||
|
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forRoot([{
|
RouterModule.forRoot([
|
||||||
path: '', canActivate: [AuthBlockingGuard],
|
{ path: INTERNAL_SERVER_ERROR, component: ThemedPageInternalServerErrorComponent },
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
canActivate: [AuthBlockingGuard],
|
||||||
|
canActivateChild: [ServerCheckGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
{ path: 'reload/:rnd', component: ThemedPageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] },
|
{
|
||||||
|
path: 'reload/:rnd',
|
||||||
|
component: ThemedPageNotFoundComponent,
|
||||||
|
pathMatch: 'full',
|
||||||
|
canActivate: [ReloadGuard]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
loadChildren: () => import('./home-page/home-page.module')
|
loadChildren: () => import('./home-page/home-page.module')
|
||||||
@@ -89,7 +102,8 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
.then((m) => m.ItemPageModule),
|
.then((m) => m.ItemPageModule),
|
||||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
canActivate: [EndUserAgreementCurrentUserGuard]
|
||||||
},
|
},
|
||||||
{ path: 'entities/:entity-type',
|
{
|
||||||
|
path: 'entities/:entity-type',
|
||||||
loadChildren: () => import('./item-page/item-page.module')
|
loadChildren: () => import('./item-page/item-page.module')
|
||||||
.then((m) => m.ItemPageModule),
|
.then((m) => m.ItemPageModule),
|
||||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
canActivate: [EndUserAgreementCurrentUserGuard]
|
||||||
@@ -133,12 +147,12 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
loadChildren: () => import('./login-page/login-page.module')
|
loadChildren: () => import('./login-page/login-page.module')
|
||||||
.then((m) => m.LoginPageModule),
|
.then((m) => m.LoginPageModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'logout',
|
path: 'logout',
|
||||||
loadChildren: () => import('./logout-page/logout-page.module')
|
loadChildren: () => import('./logout-page/logout-page.module')
|
||||||
.then((m) => m.LogoutPageModule),
|
.then((m) => m.LogoutPageModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'submit',
|
path: 'submit',
|
||||||
@@ -178,7 +192,7 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: INFO_MODULE_PATH,
|
path: INFO_MODULE_PATH,
|
||||||
loadChildren: () => import('./info/info.module').then((m) => m.InfoModule),
|
loadChildren: () => import('./info/info.module').then((m) => m.InfoModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: REQUEST_COPY_MODULE_PATH,
|
path: REQUEST_COPY_MODULE_PATH,
|
||||||
@@ -192,7 +206,7 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
{
|
{
|
||||||
path: 'statistics',
|
path: 'statistics',
|
||||||
loadChildren: () => import('./statistics-page/statistics-page-routing.module')
|
loadChildren: () => import('./statistics-page/statistics-page-routing.module')
|
||||||
.then((m) => m.StatisticsPageRoutingModule),
|
.then((m) => m.StatisticsPageRoutingModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ACCESS_CONTROL_MODULE_PATH,
|
path: ACCESS_CONTROL_MODULE_PATH,
|
||||||
@@ -200,9 +214,10 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
canActivate: [GroupAdministratorGuard],
|
canActivate: [GroupAdministratorGuard],
|
||||||
},
|
},
|
||||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||||
]}
|
]
|
||||||
],{
|
}
|
||||||
onSameUrlNavigation: 'reload',
|
], {
|
||||||
|
onSameUrlNavigation: 'reload',
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
|
@@ -54,11 +54,10 @@ import { ThemedFooterComponent } from './footer/themed-footer.component';
|
|||||||
import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.component';
|
import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.component';
|
||||||
import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component';
|
import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component';
|
||||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
|
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||||
|
import { PageInternalServerErrorComponent } from './page-internal-server-error/page-internal-server-error.component';
|
||||||
|
|
||||||
import { UUIDService } from './core/shared/uuid.service';
|
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
||||||
import { CookieService } from './core/services/cookie.service';
|
|
||||||
|
|
||||||
import { AppConfig, APP_CONFIG } from '../config/app-config.interface';
|
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
return environment;
|
return environment;
|
||||||
@@ -156,21 +155,6 @@ const PROVIDERS = [
|
|||||||
useClass: LogInterceptor,
|
useClass: LogInterceptor,
|
||||||
multi: true
|
multi: true
|
||||||
},
|
},
|
||||||
// insert the unique id of the user that is using the application utilizing cookies
|
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
useFactory: (cookieService: CookieService, uuidService: UUIDService) => {
|
|
||||||
const correlationId = cookieService.get('CORRELATION-ID');
|
|
||||||
|
|
||||||
// Check if cookie exists, if don't, set it with unique id
|
|
||||||
if (!correlationId) {
|
|
||||||
cookieService.set('CORRELATION-ID', uuidService.generate());
|
|
||||||
}
|
|
||||||
return () => true;
|
|
||||||
},
|
|
||||||
multi: true,
|
|
||||||
deps: [CookieService, UUIDService]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
||||||
useValue: ValidateEmailErrorStateMatcher
|
useValue: ValidateEmailErrorStateMatcher
|
||||||
@@ -199,7 +183,9 @@ const DECLARATIONS = [
|
|||||||
ThemedBreadcrumbsComponent,
|
ThemedBreadcrumbsComponent,
|
||||||
ForbiddenComponent,
|
ForbiddenComponent,
|
||||||
ThemedForbiddenComponent,
|
ThemedForbiddenComponent,
|
||||||
IdleModalComponent
|
IdleModalComponent,
|
||||||
|
ThemedPageInternalServerErrorComponent,
|
||||||
|
PageInternalServerErrorComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXPORTS = [
|
const EXPORTS = [
|
||||||
|
@@ -49,6 +49,7 @@ import {
|
|||||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||||
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
||||||
|
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
@@ -69,6 +70,7 @@ export interface AppState {
|
|||||||
communityList: CommunityListState;
|
communityList: CommunityListState;
|
||||||
epeopleRegistry: EPeopleRegistryState;
|
epeopleRegistry: EPeopleRegistryState;
|
||||||
groupRegistry: GroupRegistryState;
|
groupRegistry: GroupRegistryState;
|
||||||
|
correlationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -90,6 +92,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
communityList: CommunityListReducer,
|
communityList: CommunityListReducer,
|
||||||
epeopleRegistry: ePeopleRegistryReducer,
|
epeopleRegistry: ePeopleRegistryReducer,
|
||||||
groupRegistry: groupRegistryReducer,
|
groupRegistry: groupRegistryReducer,
|
||||||
|
correlationId: correlationIdReducer
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerStateSelector = (state: AppState) => state.router;
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
@@ -4,6 +4,8 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
|
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
|
||||||
import { BitstreamPageRoutingModule } from './bitstream-page-routing.module';
|
import { BitstreamPageRoutingModule } from './bitstream-page-routing.module';
|
||||||
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
||||||
|
import { FormModule } from '../shared/form/form.module';
|
||||||
|
import { ResourcePoliciesModule } from '../shared/resource-policies/resource-policies.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module handles all components that are necessary for Bitstream related pages
|
* This module handles all components that are necessary for Bitstream related pages
|
||||||
@@ -12,7 +14,9 @@ import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bit
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
BitstreamPageRoutingModule
|
BitstreamPageRoutingModule,
|
||||||
|
FormModule,
|
||||||
|
ResourcePoliciesModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BitstreamAuthorizationsComponent,
|
BitstreamAuthorizationsComponent,
|
||||||
|
@@ -63,7 +63,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||||
this.updatePageWithItems(searchOptions, this.value);
|
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
}));
|
}));
|
||||||
|
@@ -99,6 +99,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
value = '';
|
value = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authority key (may be undefined) associated with {@link #value}.
|
||||||
|
*/
|
||||||
|
authority: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current startsWith option (fetched and updated from query-params)
|
* The current startsWith option (fetched and updated from query-params)
|
||||||
*/
|
*/
|
||||||
@@ -123,11 +128,12 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
|
this.authority = params.authority;
|
||||||
this.value = +params.value || params.value || '';
|
this.value = +params.value || params.value || '';
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||||
if (isNotEmpty(this.value)) {
|
if (isNotEmpty(this.value)) {
|
||||||
this.updatePageWithItems(searchOptions, this.value);
|
this.updatePageWithItems(searchOptions, this.value, this.authority);
|
||||||
} else {
|
} else {
|
||||||
this.updatePage(searchOptions);
|
this.updatePage(searchOptions);
|
||||||
}
|
}
|
||||||
@@ -166,8 +172,8 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
* scope: string }
|
* scope: string }
|
||||||
* @param value The value of the browse-entry to display items for
|
* @param value The value of the browse-entry to display items for
|
||||||
*/
|
*/
|
||||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string) {
|
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
||||||
this.items$ = this.browseService.getBrowseItemsFor(value, searchOptions);
|
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -46,7 +46,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined);
|
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
@@ -6,6 +6,7 @@ import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-
|
|||||||
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -17,6 +18,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
ComcolModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
@@ -28,8 +28,8 @@ import { NONE_ENTITY_TYPE } from '../../core/shared/item-relationships/item-type
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-form',
|
selector: 'ds-collection-form',
|
||||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||||
})
|
})
|
||||||
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
||||||
/**
|
/**
|
||||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { CollectionFormComponent } from './collection-form.component';
|
import { CollectionFormComponent } from './collection-form.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
|
FormModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -33,7 +33,7 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
|||||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import {
|
import {
|
||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
|
@@ -9,10 +9,11 @@ import { Collection } from '../../core/shared/collection.model';
|
|||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
getRemoteDataPayload,
|
getAllSucceededRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
toDSpaceObjectListRD,
|
getFirstSucceededRemoteData,
|
||||||
getFirstCompletedRemoteData, getAllSucceededRemoteData
|
getRemoteDataPayload,
|
||||||
|
toDSpaceObjectListRD
|
||||||
} from '../../core/shared/operators';
|
} from '../../core/shared/operators';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||||
@@ -24,7 +25,7 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
|||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
import { NoContent } from '../../core/shared/NoContent.model';
|
import { NoContent } from '../../core/shared/NoContent.model';
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import {
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs';
|
||||||
BehaviorSubject,
|
|
||||||
combineLatest as observableCombineLatest,
|
|
||||||
Observable,
|
|
||||||
Subject
|
|
||||||
} from 'rxjs';
|
|
||||||
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
@@ -103,20 +98,20 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig);
|
const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig);
|
||||||
|
|
||||||
this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe(
|
this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe(
|
||||||
switchMap(([currentPagination, currentSort ]) => this.collectionRD$.pipe(
|
switchMap(([currentPagination, currentSort]) => this.collectionRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
map((rd) => rd.payload.id),
|
map((rd) => rd.payload.id),
|
||||||
switchMap((id: string) => {
|
switchMap((id: string) => {
|
||||||
return this.searchService.search(
|
return this.searchService.search(
|
||||||
new PaginatedSearchOptions({
|
new PaginatedSearchOptions({
|
||||||
scope: id,
|
scope: id,
|
||||||
pagination: currentPagination,
|
pagination: currentPagination,
|
||||||
sort: currentSort,
|
sort: currentSort,
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
dsoTypes: [DSpaceObjectType.ITEM]
|
||||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
}),
|
}),
|
||||||
startWith(undefined) // Make sure switching pages shows loading component
|
startWith(undefined) // Make sure switching pages shows loading component
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ import { SearchService } from '../core/shared/search/search.service';
|
|||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CollectionFormModule } from './collection-form/collection-form.module';
|
import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,7 +23,8 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
|
|||||||
CollectionPageRoutingModule,
|
CollectionPageRoutingModule,
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
EditItemPageModule,
|
EditItemPageModule,
|
||||||
CollectionFormModule
|
CollectionFormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
|
@@ -2,12 +2,12 @@ import { Component } from '@angular/core';
|
|||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { RouteService } from '../../core/services/route.service';
|
import { RouteService } from '../../core/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {RequestService} from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that represents the page where a user can create a new Collection
|
* Component that represents the page where a user can create a new Collection
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {RequestService} from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that represents the page where a user can delete an existing Collection
|
* Component that represents the page where a user can delete an existing Collection
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
@@ -12,6 +12,7 @@ import { RequestService } from '../../../core/data/request.service';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
describe('CollectionRolesComponent', () => {
|
describe('CollectionRolesComponent', () => {
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ describe('CollectionRolesComponent', () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-collection',
|
selector: 'ds-edit-collection',
|
||||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||||
})
|
})
|
||||||
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
||||||
type = 'collection';
|
type = 'collection';
|
||||||
|
@@ -10,6 +10,9 @@ import { CollectionSourceComponent } from './collection-source/collection-source
|
|||||||
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||||
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
||||||
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
||||||
|
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -19,7 +22,10 @@ import { CollectionSourceControlsComponent } from './collection-source/collectio
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EditCollectionPageRoutingModule,
|
EditCollectionPageRoutingModule,
|
||||||
CollectionFormModule
|
CollectionFormModule,
|
||||||
|
ResourcePoliciesModule,
|
||||||
|
FormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditCollectionPageComponent,
|
EditCollectionPageComponent,
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
DynamicTextAreaModel
|
DynamicTextAreaModel
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
@@ -19,8 +19,8 @@ import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-form',
|
selector: 'ds-community-form',
|
||||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||||
})
|
})
|
||||||
export class CommunityFormComponent extends ComColFormComponent<Community> {
|
export class CommunityFormComponent extends ComColFormComponent<Community> {
|
||||||
/**
|
/**
|
||||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { CommunityFormComponent } from './community-form.component';
|
import { CommunityFormComponent } from './community-form.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
|
FormModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -12,6 +12,7 @@ import { DeleteCommunityPageComponent } from './delete-community-page/delete-com
|
|||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CommunityFormModule } from './community-form/community-form.module';
|
import { CommunityFormModule } from './community-form/community-form.module';
|
||||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
const DECLARATIONS = [CommunityPageComponent,
|
const DECLARATIONS = [CommunityPageComponent,
|
||||||
ThemedCommunityPageComponent,
|
ThemedCommunityPageComponent,
|
||||||
@@ -26,7 +27,8 @@ const DECLARATIONS = [CommunityPageComponent,
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
CommunityPageRoutingModule,
|
CommunityPageRoutingModule,
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
CommunityFormModule
|
CommunityFormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
|
@@ -3,7 +3,7 @@ import { Community } from '../../core/shared/community.model';
|
|||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { RouteService } from '../../core/services/route.service';
|
import { RouteService } from '../../core/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
@@ -2,10 +2,10 @@ import { Component } from '@angular/core';
|
|||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {RequestService} from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that represents the page where a user can delete an existing Community
|
* Component that represents the page where a user can delete an existing Community
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||||
|
@@ -12,6 +12,7 @@ import { SharedModule } from '../../../shared/shared.module';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
describe('CommunityRolesComponent', () => {
|
describe('CommunityRolesComponent', () => {
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ describe('CommunityRolesComponent', () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||||
import { getCommunityPageRoute } from '../community-page-routing-paths';
|
import { getCommunityPageRoute } from '../community-page-routing-paths';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +9,7 @@ import { getCommunityPageRoute } from '../community-page-routing-paths';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-community',
|
selector: 'ds-edit-community',
|
||||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||||
})
|
})
|
||||||
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
||||||
type = 'community';
|
type = 'community';
|
||||||
|
@@ -8,6 +8,8 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
|
|||||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||||
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||||
import { CommunityFormModule } from '../community-form/community-form.module';
|
import { CommunityFormModule } from '../community-form/community-form.module';
|
||||||
|
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -17,7 +19,9 @@ import { CommunityFormModule } from '../community-form/community-form.module';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EditCommunityPageRoutingModule,
|
EditCommunityPageRoutingModule,
|
||||||
CommunityFormModule
|
CommunityFormModule,
|
||||||
|
ComcolModule,
|
||||||
|
ResourcePoliciesModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditCommunityPageComponent,
|
EditCommunityPageComponent,
|
||||||
|
@@ -129,6 +129,7 @@ describe('BrowseService', () => {
|
|||||||
describe('getBrowseEntriesFor and findList', () => {
|
describe('getBrowseEntriesFor and findList', () => {
|
||||||
// should contain special characters such that url encoding can be tested as well
|
// should contain special characters such that url encoding can be tested as well
|
||||||
const mockAuthorName = 'Donald Smith & Sons';
|
const mockAuthorName = 'Donald Smith & Sons';
|
||||||
|
const mockAuthorityKey = 'some authority key ?=;';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
@@ -155,7 +156,7 @@ describe('BrowseService', () => {
|
|||||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||||
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, undefined, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||||
@@ -164,6 +165,20 @@ describe('BrowseService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
describe('when getBrowseItemsFor is called with a valid filter value and authority key', () => {
|
||||||
|
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||||
|
const expected = browseDefinitions[1]._links.items.href +
|
||||||
|
'?filterValue=' + encodeURIComponent(mockAuthorName) +
|
||||||
|
'&filterAuthority=' + encodeURIComponent(mockAuthorityKey);
|
||||||
|
|
||||||
|
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, mockAuthorityKey, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||||
|
a: expected
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseURLFor', () => {
|
describe('getBrowseURLFor', () => {
|
||||||
|
@@ -105,7 +105,7 @@ export class BrowseService {
|
|||||||
* @param options Options to narrow down your search
|
* @param options Options to narrow down your search
|
||||||
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||||
*/
|
*/
|
||||||
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
getBrowseItemsFor(filterValue: string, filterAuthority: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
const href$ = this.getBrowseDefinitions().pipe(
|
const href$ = this.getBrowseDefinitions().pipe(
|
||||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
@@ -132,6 +132,9 @@ export class BrowseService {
|
|||||||
if (isNotEmpty(filterValue)) {
|
if (isNotEmpty(filterValue)) {
|
||||||
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
||||||
}
|
}
|
||||||
|
if (isNotEmpty(filterAuthority)) {
|
||||||
|
args.push(`filterAuthority=${encodeURIComponent(filterAuthority)}`);
|
||||||
|
}
|
||||||
if (isNotEmpty(args)) {
|
if (isNotEmpty(args)) {
|
||||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
}
|
}
|
||||||
|
@@ -163,6 +163,7 @@ import { RootDataService } from './data/root-data.service';
|
|||||||
import { Root } from './data/root.model';
|
import { Root } from './data/root.model';
|
||||||
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
||||||
import { SequenceService } from './shared/sequence.service';
|
import { SequenceService } from './shared/sequence.service';
|
||||||
|
import { GroupDataService } from './eperson/group-data.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -285,6 +286,7 @@ const PROVIDERS = [
|
|||||||
VocabularyService,
|
VocabularyService,
|
||||||
VocabularyTreeviewService,
|
VocabularyTreeviewService,
|
||||||
SequenceService,
|
SequenceService,
|
||||||
|
GroupDataService
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -20,7 +20,7 @@ import { PaginatedList } from './paginated-list.model';
|
|||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { FindListOptions, GetRequest } from './request.models';
|
import { FindListOptions, GetRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
import { RequestEntryState } from './request.reducer';
|
import { RequestEntryState } from './request.reducer';
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { FindListOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../shared/search/models/search-filter-config.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
import { FacetConfigResponse } from '../../shared/search/facet-config-response.model';
|
import { FacetConfigResponse } from '../../shared/search/models/facet-config-response.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FacetConfigResponseParsingService extends DspaceRestResponseParsingService {
|
export class FacetConfigResponseParsingService extends DspaceRestResponseParsingService {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { FacetValue } from '../../shared/search/facet-value.model';
|
import { FacetValue } from '../../shared/search/models/facet-value.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { FacetValues } from '../../shared/search/facet-values.model';
|
import { FacetValues } from '../../shared/search/models/facet-values.model';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
|
||||||
|
import { AuthorizationDataService } from '../authorization-data.service';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { FeatureID } from '../feature-id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group
|
||||||
|
* management rights
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class StatisticsAdministratorGuard extends SingleFeatureAuthorizationGuard {
|
||||||
|
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
|
||||||
|
super(authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check group management rights
|
||||||
|
*/
|
||||||
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
|
return observableOf(FeatureID.CanViewUsageStatistics);
|
||||||
|
}
|
||||||
|
}
|
@@ -13,6 +13,7 @@ export enum FeatureID {
|
|||||||
CanManageGroup = 'canManageGroup',
|
CanManageGroup = 'canManageGroup',
|
||||||
IsCollectionAdmin = 'isCollectionAdmin',
|
IsCollectionAdmin = 'isCollectionAdmin',
|
||||||
IsCommunityAdmin = 'isCommunityAdmin',
|
IsCommunityAdmin = 'isCommunityAdmin',
|
||||||
|
CanChangePassword = 'canChangePassword',
|
||||||
CanDownload = 'canDownload',
|
CanDownload = 'canDownload',
|
||||||
CanRequestACopy = 'canRequestACopy',
|
CanRequestACopy = 'canRequestACopy',
|
||||||
CanManageVersions = 'canManageVersions',
|
CanManageVersions = 'canManageVersions',
|
||||||
@@ -25,4 +26,5 @@ export enum FeatureID {
|
|||||||
CanEditVersion = 'canEditVersion',
|
CanEditVersion = 'canEditVersion',
|
||||||
CanDeleteVersion = 'canDeleteVersion',
|
CanDeleteVersion = 'canDeleteVersion',
|
||||||
CanCreateVersion = 'canCreateVersion',
|
CanCreateVersion = 'canCreateVersion',
|
||||||
|
CanViewUsageStatistics = 'canViewUsageStatistics',
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ import { PaginatedList } from './paginated-list.model';
|
|||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { DeleteRequest, FindListOptions, GetRequest, PostRequest, PutRequest, RestRequest } from './request.models';
|
import { DeleteRequest, FindListOptions, GetRequest, PostRequest, PutRequest, RestRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Bundle } from '../shared/bundle.model';
|
import { Bundle } from '../shared/bundle.model';
|
||||||
import { MetadataMap } from '../shared/metadata.models';
|
import { MetadataMap } from '../shared/metadata.models';
|
||||||
import { BundleDataService } from './bundle-data.service';
|
import { BundleDataService } from './bundle-data.service';
|
||||||
|
@@ -5,9 +5,9 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.util
|
|||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { buildPaginatedList } from './paginated-list.model';
|
import { buildPaginatedList } from './paginated-list.model';
|
||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import { skip, take } from 'rxjs/operators';
|
import { skip, take } from 'rxjs/operators';
|
||||||
import { ExternalSource } from '../shared/external-source.model';
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { ExternalSourceService } from './external-source.service';
|
import { ExternalSourceService } from './external-source.service';
|
||||||
import { SearchService } from '../shared/search/search.service';
|
import { SearchService } from '../shared/search/search.service';
|
||||||
import { concat, distinctUntilChanged, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
import { concat, distinctUntilChanged, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Observable, ReplaySubject } from 'rxjs';
|
import { Observable, ReplaySubject } from 'rxjs';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
|
@@ -4,7 +4,7 @@ import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
|||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||||
import { MetadataMap, MetadataValue } from '../shared/metadata.models';
|
import { MetadataMap, MetadataValue } from '../shared/metadata.models';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
|
||||||
|
@@ -1,13 +1,16 @@
|
|||||||
import { RootDataService } from './root-data.service';
|
import { RootDataService } from './root-data.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { Root } from './root.model';
|
import { Root } from './root.model';
|
||||||
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('RootDataService', () => {
|
describe('RootDataService', () => {
|
||||||
let service: RootDataService;
|
let service: RootDataService;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
|
let restService;
|
||||||
let rootEndpoint;
|
let rootEndpoint;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -15,7 +18,10 @@ describe('RootDataService', () => {
|
|||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getRootHref: rootEndpoint
|
getRootHref: rootEndpoint
|
||||||
});
|
});
|
||||||
service = new RootDataService(null, null, null, null, halService, null, null, null);
|
restService = jasmine.createSpyObj('halService', {
|
||||||
|
get: jasmine.createSpy('get')
|
||||||
|
});
|
||||||
|
service = new RootDataService(null, null, null, null, halService, null, null, null, restService);
|
||||||
(service as any).dataService = jasmine.createSpyObj('dataService', {
|
(service as any).dataService = jasmine.createSpyObj('dataService', {
|
||||||
findByHref: createSuccessfulRemoteDataObject$({})
|
findByHref: createSuccessfulRemoteDataObject$({})
|
||||||
});
|
});
|
||||||
@@ -35,4 +41,37 @@ describe('RootDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('checkServerAvailability', () => {
|
||||||
|
let result$: Observable<boolean>;
|
||||||
|
|
||||||
|
it('should return observable of true when root endpoint is available', () => {
|
||||||
|
const mockResponse = {
|
||||||
|
statusCode: 200,
|
||||||
|
statusText: 'OK'
|
||||||
|
} as RawRestResponse;
|
||||||
|
|
||||||
|
restService.get.and.returnValue(of(mockResponse));
|
||||||
|
result$ = service.checkServerAvailability();
|
||||||
|
|
||||||
|
expect(result$).toBeObservable(cold('(a|)', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return observable of false when root endpoint is not available', () => {
|
||||||
|
const mockResponse = {
|
||||||
|
statusCode: 500,
|
||||||
|
statusText: 'Internal Server Error'
|
||||||
|
} as RawRestResponse;
|
||||||
|
|
||||||
|
restService.get.and.returnValue(of(mockResponse));
|
||||||
|
result$ = service.checkServerAvailability();
|
||||||
|
|
||||||
|
expect(result$).toBeObservable(cold('(a|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -17,6 +17,10 @@ import { RemoteData } from './remote-data';
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { FindListOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
|
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
|
||||||
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { of } from 'rxjs/internal/observable/of';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
@@ -59,10 +63,24 @@ export class RootDataService {
|
|||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
protected http: HttpClient,
|
protected http: HttpClient,
|
||||||
protected comparator: DefaultChangeAnalyzer<Root>) {
|
protected comparator: DefaultChangeAnalyzer<Root>,
|
||||||
|
protected restService: DspaceRestService) {
|
||||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if root endpoint is available
|
||||||
|
*/
|
||||||
|
checkServerAvailability(): Observable<boolean> {
|
||||||
|
return this.restService.get(this.halService.getRootHref()).pipe(
|
||||||
|
catchError((err ) => {
|
||||||
|
console.error(err);
|
||||||
|
return of(false);
|
||||||
|
}),
|
||||||
|
map((res: RawRestResponse) => res.statusCode === 200)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the {@link Root} object of the REST API
|
* Find the {@link Root} object of the REST API
|
||||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
@@ -106,5 +124,12 @@ export class RootDataService {
|
|||||||
findAllByHref(href: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Root>[]): Observable<RemoteData<PaginatedList<Root>>> {
|
findAllByHref(href: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Root>[]): Observable<RemoteData<PaginatedList<Root>>> {
|
||||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to sale the root endpoint cache hit
|
||||||
|
*/
|
||||||
|
invalidateRootCache() {
|
||||||
|
this.requestService.setStaleByHrefSubstring(this.halService.getRootHref());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
|
@@ -12,7 +12,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { FindListOptions, PostRequest, RestRequest } from './request.models';
|
import { FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { Version } from '../shared/version.model';
|
import { Version } from '../shared/version.model';
|
||||||
|
@@ -40,9 +40,7 @@ const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegis
|
|||||||
/**
|
/**
|
||||||
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable()
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
@dataService(GROUP)
|
@dataService(GROUP)
|
||||||
export class GroupDataService extends DataService<Group> {
|
export class GroupDataService extends DataService<Group> {
|
||||||
protected linkPath = 'groups';
|
protected linkPath = 'groups';
|
||||||
|
@@ -9,12 +9,17 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
|||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
||||||
import { RouterStub } from '../../shared/testing/router.stub';
|
import { RouterStub } from '../../shared/testing/router.stub';
|
||||||
|
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||||
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
import { appReducers, storeModuleConfig } from '../../app.reducer';
|
||||||
|
|
||||||
|
|
||||||
describe('LogInterceptor', () => {
|
describe('LogInterceptor', () => {
|
||||||
let service: DspaceRestService;
|
let service: DspaceRestService;
|
||||||
let httpMock: HttpTestingController;
|
let httpMock: HttpTestingController;
|
||||||
let cookieService: CookieService;
|
let cookieService: CookieService;
|
||||||
|
let correlationIdService: CorrelationIdService;
|
||||||
const router = Object.assign(new RouterStub(),{url : '/statistics'});
|
const router = Object.assign(new RouterStub(),{url : '/statistics'});
|
||||||
|
|
||||||
// Mock payload/statuses are dummy content as we are not testing the results
|
// Mock payload/statuses are dummy content as we are not testing the results
|
||||||
@@ -28,7 +33,10 @@ describe('LogInterceptor', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [HttpClientTestingModule],
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DspaceRestService,
|
DspaceRestService,
|
||||||
// LogInterceptor,
|
// LogInterceptor,
|
||||||
@@ -39,14 +47,18 @@ describe('LogInterceptor', () => {
|
|||||||
},
|
},
|
||||||
{ provide: CookieService, useValue: new CookieServiceMock() },
|
{ provide: CookieService, useValue: new CookieServiceMock() },
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: CorrelationIdService, useClass: CorrelationIdService },
|
||||||
|
{ provide: UUIDService, useClass: UUIDService },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
service = TestBed.get(DspaceRestService);
|
service = TestBed.inject(DspaceRestService);
|
||||||
httpMock = TestBed.get(HttpTestingController);
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
cookieService = TestBed.get(CookieService);
|
cookieService = TestBed.inject(CookieService);
|
||||||
|
correlationIdService = TestBed.inject(CorrelationIdService);
|
||||||
|
|
||||||
cookieService.set('CORRELATION-ID','123455');
|
cookieService.set('CORRELATION-ID','123455');
|
||||||
|
correlationIdService.initCorrelationId();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3,9 +3,8 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { CookieService } from '../services/cookie.service';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log Interceptor intercepting Http Requests & Responses to
|
* Log Interceptor intercepting Http Requests & Responses to
|
||||||
@@ -15,12 +14,12 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class LogInterceptor implements HttpInterceptor {
|
export class LogInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
constructor(private cookieService: CookieService, private router: Router) {}
|
constructor(private cidService: CorrelationIdService, private router: Router) {}
|
||||||
|
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
// Get Unique id of the user from the cookies
|
// Get the correlation id for the user from the store
|
||||||
const correlationId = this.cookieService.get('CORRELATION-ID');
|
const correlationId = this.cidService.getCorrelationId();
|
||||||
|
|
||||||
// Add headers from the intercepted request
|
// Add headers from the intercepted request
|
||||||
let headers = request.headers;
|
let headers = request.headers;
|
||||||
|
68
src/app/core/server-check/server-check.guard.spec.ts
Normal file
68
src/app/core/server-check/server-check.guard.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { ServerCheckGuard } from './server-check.guard';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { getPageInternalServerErrorRoute } from '../../app-routing-paths';
|
||||||
|
import { RootDataService } from '../data/root-data.service';
|
||||||
|
import SpyObj = jasmine.SpyObj;
|
||||||
|
|
||||||
|
describe('ServerCheckGuard', () => {
|
||||||
|
let guard: ServerCheckGuard;
|
||||||
|
let router: SpyObj<Router>;
|
||||||
|
let rootDataServiceStub: SpyObj<RootDataService>;
|
||||||
|
|
||||||
|
rootDataServiceStub = jasmine.createSpyObj('RootDataService', {
|
||||||
|
checkServerAvailability: jasmine.createSpy('checkServerAvailability'),
|
||||||
|
invalidateRootCache: jasmine.createSpy('invalidateRootCache')
|
||||||
|
});
|
||||||
|
router = jasmine.createSpyObj('Router', {
|
||||||
|
navigateByUrl: jasmine.createSpy('navigateByUrl')
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
guard = new ServerCheckGuard(router, rootDataServiceStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
router.navigateByUrl.calls.reset();
|
||||||
|
rootDataServiceStub.invalidateRootCache.calls.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when root endpoint has succeeded', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
rootDataServiceStub.checkServerAvailability.and.returnValue(of(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not redirect to error page', () => {
|
||||||
|
guard.canActivateChild({} as any, {} as any).pipe(
|
||||||
|
take(1)
|
||||||
|
).subscribe((canActivate: boolean) => {
|
||||||
|
expect(canActivate).toEqual(true);
|
||||||
|
expect(rootDataServiceStub.invalidateRootCache).not.toHaveBeenCalled();
|
||||||
|
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when root endpoint has not succeeded', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
rootDataServiceStub.checkServerAvailability.and.returnValue(of(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to error page', () => {
|
||||||
|
guard.canActivateChild({} as any, {} as any).pipe(
|
||||||
|
take(1)
|
||||||
|
).subscribe((canActivate: boolean) => {
|
||||||
|
expect(canActivate).toEqual(false);
|
||||||
|
expect(rootDataServiceStub.invalidateRootCache).toHaveBeenCalled();
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalledWith(getPageInternalServerErrorRoute());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
39
src/app/core/server-check/server-check.guard.ts
Normal file
39
src/app/core/server-check/server-check.guard.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { take, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { RootDataService } from '../data/root-data.service';
|
||||||
|
import { getPageInternalServerErrorRoute } from '../../app-routing-paths';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A guard that checks if root api endpoint is reachable.
|
||||||
|
* If not redirect to 500 error page
|
||||||
|
*/
|
||||||
|
export class ServerCheckGuard implements CanActivateChild {
|
||||||
|
constructor(private router: Router, private rootDataService: RootDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when root api endpoint is reachable.
|
||||||
|
*/
|
||||||
|
canActivateChild(
|
||||||
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): Observable<boolean> {
|
||||||
|
|
||||||
|
return this.rootDataService.checkServerAvailability().pipe(
|
||||||
|
take(1),
|
||||||
|
tap((isAvailable: boolean) => {
|
||||||
|
if (!isAvailable) {
|
||||||
|
this.rootDataService.invalidateRootCache();
|
||||||
|
this.router.navigateByUrl(getPageInternalServerErrorRoute());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -31,4 +31,8 @@ export class ServerResponseService {
|
|||||||
setNotFound(message = 'Not found'): this {
|
setNotFound(message = 'Not found'): this {
|
||||||
return this.setStatus(404, message);
|
return this.setStatus(404, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInternalServerError(message = 'Internal Server Error'): this {
|
||||||
|
return this.setStatus(500, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
withLatestFrom
|
withLatestFrom
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { RestRequest } from '../data/request.models';
|
import { RestRequest } from '../data/request.models';
|
||||||
|
@@ -2,8 +2,8 @@ import { SearchConfigurationService } from './search-configuration.service';
|
|||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
|
||||||
|
@@ -3,31 +3,27 @@ import { ActivatedRoute, Params } from '@angular/router';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
merge as observableMerge,
|
merge as observableMerge,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription
|
Subscription
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, startWith } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
import { SearchOptions } from '../../../shared/search/models/search-options.model';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
import { DSpaceObjectType } from '../dspace-object-type.model';
|
import { DSpaceObjectType } from '../dspace-object-type.model';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
import { RouteService } from '../../services/route.service';
|
import { RouteService } from '../../services/route.service';
|
||||||
import {
|
import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData } from '../operators';
|
||||||
getAllSucceededRemoteDataPayload,
|
|
||||||
getFirstSucceededRemoteData
|
|
||||||
} from '../operators';
|
|
||||||
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { SearchConfig } from './search-filters/search-config.model';
|
import { SearchConfig, SortConfig } from './search-filters/search-config.model';
|
||||||
import { SearchService } from './search.service';
|
import { SearchService } from './search.service';
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { PaginationService } from '../../pagination/pagination.service';
|
import { PaginationService } from '../../pagination/pagination.service';
|
||||||
|
import { ViewMode } from '../view-mode.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that performs all actions that have to do with the current search configuration
|
* Service that performs all actions that have to do with the current search configuration
|
||||||
@@ -35,7 +31,21 @@ import { PaginationService } from '../../pagination/pagination.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchConfigurationService implements OnDestroy {
|
export class SearchConfigurationService implements OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default pagination id
|
||||||
|
*/
|
||||||
public paginationID = 'spc';
|
public paginationID = 'spc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current search options
|
||||||
|
*/
|
||||||
|
public searchOptions: BehaviorSubject<SearchOptions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current search options including pagination and sort
|
||||||
|
*/
|
||||||
|
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default pagination settings
|
* Default pagination settings
|
||||||
*/
|
*/
|
||||||
@@ -45,16 +55,6 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
currentPage: 1
|
currentPage: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Default sort settings
|
|
||||||
*/
|
|
||||||
protected defaultSort = new SortOptions('score', SortDirection.DESC);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default configuration parameter setting
|
|
||||||
*/
|
|
||||||
protected defaultConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default scope setting
|
* Default scope setting
|
||||||
*/
|
*/
|
||||||
@@ -71,23 +71,14 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
protected _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
|
protected _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the current search options
|
* A map of subscriptions to unsubscribe from on destroy
|
||||||
*/
|
*/
|
||||||
public searchOptions: BehaviorSubject<SearchOptions>;
|
protected subs: Map<string, Subscription[]> = new Map<string, Subscription[]>(null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits the current search options including pagination and sort
|
|
||||||
*/
|
|
||||||
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of subscriptions to unsubscribe from on destroy
|
|
||||||
*/
|
|
||||||
protected subs: Subscription[] = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the search options
|
* Initialize the search options
|
||||||
* @param {RouteService} routeService
|
* @param {RouteService} routeService
|
||||||
|
* @param {PaginationService} paginationService
|
||||||
* @param {ActivatedRoute} route
|
* @param {ActivatedRoute} route
|
||||||
*/
|
*/
|
||||||
constructor(protected routeService: RouteService,
|
constructor(protected routeService: RouteService,
|
||||||
@@ -98,29 +89,28 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the search options
|
* Default values for the Search Options
|
||||||
*/
|
*/
|
||||||
protected initDefaults() {
|
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
||||||
this.defaults
|
if (hasNoValue(this._defaults)) {
|
||||||
.pipe(getFirstSucceededRemoteData())
|
const options = new PaginatedSearchOptions({
|
||||||
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
pagination: this.defaultPagination,
|
||||||
const defs = defRD.payload;
|
scope: this.defaultScope,
|
||||||
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
query: this.defaultQuery
|
||||||
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
});
|
||||||
this.subs.push(this.subscribeToSearchOptions(defs));
|
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
||||||
this.subs.push(this.subscribeToPaginatedSearchOptions(defs.pagination.id, defs));
|
}
|
||||||
}
|
return this._defaults;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current configuration string
|
* @returns {Observable<string>} Emits the current configuration string
|
||||||
*/
|
*/
|
||||||
getCurrentConfiguration(defaultConfiguration: string) {
|
getCurrentConfiguration(defaultConfiguration: string) {
|
||||||
return observableCombineLatest(
|
return observableCombineLatest([
|
||||||
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
||||||
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
||||||
).pipe(
|
]).pipe(
|
||||||
map(([queryConfig, routeConfig]) => {
|
map(([queryConfig, routeConfig]) => {
|
||||||
return queryConfig || routeConfig || defaultConfiguration;
|
return queryConfig || routeConfig || defaultConfiguration;
|
||||||
})
|
})
|
||||||
@@ -208,59 +198,91 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an observable of SearchConfig every time the configuration$ stream emits.
|
* @returns {Observable<string>} Emits the current view mode
|
||||||
* @param configuration$
|
|
||||||
* @param service
|
|
||||||
*/
|
*/
|
||||||
getConfigurationSearchConfigObservable(configuration$: Observable<string>, service: SearchService): Observable<SearchConfig> {
|
getCurrentViewMode(defaultViewMode: ViewMode) {
|
||||||
return configuration$.pipe(
|
return this.routeService.getQueryParameterValue('view').pipe(map((viewMode) => {
|
||||||
distinctUntilChanged(),
|
return viewMode || defaultViewMode;
|
||||||
switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)),
|
|
||||||
getAllSucceededRemoteDataPayload());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Every time searchConfig change (after a configuration change) it update the navigation with the default sort option
|
|
||||||
* and emit the new paginateSearchOptions value.
|
|
||||||
* @param configuration$
|
|
||||||
* @param service
|
|
||||||
*/
|
|
||||||
initializeSortOptionsFromConfiguration(searchConfig$: Observable<SearchConfig>) {
|
|
||||||
const subscription = searchConfig$.pipe(switchMap((searchConfig) => combineLatest([
|
|
||||||
of(searchConfig),
|
|
||||||
this.paginatedSearchOptions.pipe(take(1))
|
|
||||||
]))).subscribe(([searchConfig, searchOptions]) => {
|
|
||||||
const field = searchConfig.sortOptions[0].name;
|
|
||||||
const direction = searchConfig.sortOptions[0].sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC;
|
|
||||||
const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, {
|
|
||||||
sort: new SortOptions(field, direction)
|
|
||||||
});
|
|
||||||
this.paginationService.updateRoute(this.paginationID,
|
|
||||||
{
|
|
||||||
sortDirection: updateValue.sort.direction,
|
|
||||||
sortField: updateValue.sort.field,
|
|
||||||
});
|
|
||||||
this.paginatedSearchOptions.next(updateValue);
|
|
||||||
});
|
|
||||||
this.subs.push(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an observable of available SortOptions[] every time the searchConfig$ stream emits.
|
|
||||||
* @param searchConfig$
|
|
||||||
* @param service
|
|
||||||
*/
|
|
||||||
getConfigurationSortOptionsObservable(searchConfig$: Observable<SearchConfig>): Observable<SortOptions[]> {
|
|
||||||
return searchConfig$.pipe(map((searchConfig) => {
|
|
||||||
const sortOptions = [];
|
|
||||||
searchConfig.sortOptions.forEach(sortOption => {
|
|
||||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC));
|
|
||||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC));
|
|
||||||
});
|
|
||||||
return sortOptions;
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an observable of SearchConfig every time the configuration stream emits.
|
||||||
|
* @param configuration The search configuration
|
||||||
|
* @param service The search service to use
|
||||||
|
* @param scope The search scope if exists
|
||||||
|
*/
|
||||||
|
getConfigurationSearchConfig(configuration: string, service: SearchService, scope?: string): Observable<SearchConfig> {
|
||||||
|
return service.getSearchConfigurationFor(scope, configuration).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the SortOptions list available for the given SearchConfig
|
||||||
|
* @param searchConfig The SearchConfig object
|
||||||
|
*/
|
||||||
|
getConfigurationSortOptions(searchConfig: SearchConfig): SortOptions[] {
|
||||||
|
return searchConfig.sortOptions.map((entry: SortConfig) => ({
|
||||||
|
field: entry.name,
|
||||||
|
direction: entry.sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaginationId(paginationId): void {
|
||||||
|
if (isNotEmpty(paginationId)) {
|
||||||
|
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
||||||
|
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, {
|
||||||
|
pagination: Object.assign({}, currentValue.pagination, {
|
||||||
|
id: paginationId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// unsubscribe from subscription related to old pagination id
|
||||||
|
this.unsubscribeFromSearchOptions(this.paginationID);
|
||||||
|
|
||||||
|
// change to the new pagination id
|
||||||
|
this.paginationID = paginationId;
|
||||||
|
this.paginatedSearchOptions.next(updatedValue);
|
||||||
|
this.setSearchSubscription(this.paginationID, this.paginatedSearchOptions.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.forEach((subs: Subscription[]) => subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe())
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subs = new Map<string, Subscription[]>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the search options
|
||||||
|
*/
|
||||||
|
protected initDefaults() {
|
||||||
|
this.defaults
|
||||||
|
.pipe(getFirstSucceededRemoteData())
|
||||||
|
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
||||||
|
const defs = defRD.payload;
|
||||||
|
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
||||||
|
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
||||||
|
this.setSearchSubscription(this.paginationID, defs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setSearchSubscription(paginationID: string, defaults: PaginatedSearchOptions) {
|
||||||
|
this.unsubscribeFromSearchOptions(paginationID);
|
||||||
|
const subs = [
|
||||||
|
this.subscribeToSearchOptions(defaults),
|
||||||
|
this.subscribeToPaginatedSearchOptions(paginationID || defaults.pagination.id, defaults)
|
||||||
|
];
|
||||||
|
this.subs.set(this.paginationID, subs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
||||||
* @param {SearchOptions} defaults Default values for when no parameters are available
|
* @param {SearchOptions} defaults Default values for when no parameters are available
|
||||||
@@ -273,7 +295,8 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
this.getQueryPart(defaults.query),
|
this.getQueryPart(defaults.query),
|
||||||
this.getDSOTypePart(),
|
this.getDSOTypePart(),
|
||||||
this.getFiltersPart(),
|
this.getFiltersPart(),
|
||||||
this.getFixedFilterPart()
|
this.getFixedFilterPart(),
|
||||||
|
this.getViewModePart(defaults.view)
|
||||||
).subscribe((update) => {
|
).subscribe((update) => {
|
||||||
const currentValue: SearchOptions = this.searchOptions.getValue();
|
const currentValue: SearchOptions = this.searchOptions.getValue();
|
||||||
const updatedValue: SearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
|
const updatedValue: SearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
|
||||||
@@ -283,19 +306,21 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
|
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
|
||||||
|
* @param {string} paginationId The pagination ID
|
||||||
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
|
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
|
||||||
* @returns {Subscription} The subscription to unsubscribe from
|
* @returns {Subscription} The subscription to unsubscribe from
|
||||||
*/
|
*/
|
||||||
private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription {
|
private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription {
|
||||||
return observableMerge(
|
return observableMerge(
|
||||||
|
this.getConfigurationPart(defaults.configuration),
|
||||||
this.getPaginationPart(paginationId, defaults.pagination),
|
this.getPaginationPart(paginationId, defaults.pagination),
|
||||||
this.getSortPart(paginationId, defaults.sort),
|
this.getSortPart(paginationId, defaults.sort),
|
||||||
this.getConfigurationPart(defaults.configuration),
|
|
||||||
this.getScopePart(defaults.scope),
|
this.getScopePart(defaults.scope),
|
||||||
this.getQueryPart(defaults.query),
|
this.getQueryPart(defaults.query),
|
||||||
this.getDSOTypePart(),
|
this.getDSOTypePart(),
|
||||||
this.getFiltersPart(),
|
this.getFiltersPart(),
|
||||||
this.getFixedFilterPart()
|
this.getFixedFilterPart(),
|
||||||
|
this.getViewModePart(defaults.view)
|
||||||
).subscribe((update) => {
|
).subscribe((update) => {
|
||||||
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
||||||
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
|
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
|
||||||
@@ -304,30 +329,16 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default values for the Search Options
|
* Unsubscribe from all subscriptions related to the given paginationID
|
||||||
|
* @param paginationId The pagination id
|
||||||
*/
|
*/
|
||||||
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
private unsubscribeFromSearchOptions(paginationId: string): void {
|
||||||
if (hasNoValue(this._defaults)) {
|
if (this.subs.has(this.paginationID)) {
|
||||||
const options = new PaginatedSearchOptions({
|
this.subs.get(this.paginationID)
|
||||||
pagination: this.defaultPagination,
|
.filter((sub) => hasValue(sub))
|
||||||
configuration: this.defaultConfiguration,
|
.forEach((sub) => sub.unsubscribe());
|
||||||
sort: this.defaultSort,
|
this.subs.delete(paginationId);
|
||||||
scope: this.defaultScope,
|
|
||||||
query: this.defaultQuery
|
|
||||||
});
|
|
||||||
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
|
||||||
}
|
}
|
||||||
return this._defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.forEach((sub) => {
|
|
||||||
sub.unsubscribe();
|
|
||||||
});
|
|
||||||
this.subs = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -404,4 +415,13 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<Params>} Emits the current view mode as a partial SearchOptions object
|
||||||
|
*/
|
||||||
|
private getViewModePart(defaultViewMode: ViewMode): Observable<any> {
|
||||||
|
return this.getCurrentViewMode(defaultViewMode).pipe(map((view) => {
|
||||||
|
return { view };
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import {
|
|||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
||||||
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
|
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
|
||||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||||
import { FilterType } from '../../../shared/search/filter-type.model';
|
import { FilterType } from '../../../shared/search/models/filter-type.model';
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
|
@@ -16,7 +16,7 @@ import {
|
|||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
||||||
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
|
||||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
import { RouteService } from '../../services/route.service';
|
import { RouteService } from '../../services/route.service';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
@@ -29,7 +29,7 @@ export class SearchConfig implements CacheableObject {
|
|||||||
* The configured sort options.
|
* The configured sort options.
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
sortOptions: SortOption[];
|
sortOptions: SortConfig[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object type.
|
* The object type.
|
||||||
@@ -63,7 +63,7 @@ export interface FilterConfig {
|
|||||||
/**
|
/**
|
||||||
* Interface to model sort option's configuration.
|
* Interface to model sort option's configuration.
|
||||||
*/
|
*/
|
||||||
export interface SortOption {
|
export interface SortConfig {
|
||||||
name: string;
|
name: string;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
|||||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||||
import { HALEndpointService } from '../hal-endpoint.service';
|
import { HALEndpointService } from '../hal-endpoint.service';
|
||||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
import { RequestEntry } from '../../data/request.reducer';
|
import { RequestEntry } from '../../data/request.reducer';
|
||||||
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
|
||||||
@@ -21,11 +21,8 @@ import { RouteService } from '../../services/route.service';
|
|||||||
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
|
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
|
||||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { SearchObjects } from '../../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
|
||||||
import { PaginationService } from '../../pagination/pagination.service';
|
import { PaginationService } from '../../pagination/pagination.service';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
|
||||||
import { FindListOptions } from '../../data/request.models';
|
|
||||||
import { SearchConfigurationService } from './search-configuration.service';
|
import { SearchConfigurationService } from './search-configuration.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
|
||||||
|
@@ -14,24 +14,24 @@ import { GenericConstructor } from '../generic-constructor';
|
|||||||
import { HALEndpointService } from '../hal-endpoint.service';
|
import { HALEndpointService } from '../hal-endpoint.service';
|
||||||
import { URLCombiner } from '../../url-combiner/url-combiner';
|
import { URLCombiner } from '../../url-combiner/url-combiner';
|
||||||
import { hasValue, hasValueOperator, isNotEmpty } from '../../../shared/empty.util';
|
import { hasValue, hasValueOperator, isNotEmpty } from '../../../shared/empty.util';
|
||||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
import { SearchOptions } from '../../../shared/search/models/search-options.model';
|
||||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||||
import { SearchResponseParsingService } from '../../data/search-response-parsing.service';
|
import { SearchResponseParsingService } from '../../data/search-response-parsing.service';
|
||||||
import { SearchObjects } from '../../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
|
||||||
import { FacetValueResponseParsingService } from '../../data/facet-value-response-parsing.service';
|
import { FacetValueResponseParsingService } from '../../data/facet-value-response-parsing.service';
|
||||||
import { FacetConfigResponseParsingService } from '../../data/facet-config-response-parsing.service';
|
import { FacetConfigResponseParsingService } from '../../data/facet-config-response-parsing.service';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { CommunityDataService } from '../../data/community-data.service';
|
import { CommunityDataService } from '../../data/community-data.service';
|
||||||
import { ViewMode } from '../view-mode.model';
|
import { ViewMode } from '../view-mode.model';
|
||||||
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
|
||||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../operators';
|
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../operators';
|
||||||
import { RouteService } from '../../services/route.service';
|
import { RouteService } from '../../services/route.service';
|
||||||
import { SearchResult } from '../../../shared/search/search-result.model';
|
import { SearchResult } from '../../../shared/search/models/search-result.model';
|
||||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||||
import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator';
|
import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator';
|
||||||
import { FacetConfigResponse } from '../../../shared/search/facet-config-response.model';
|
import { FacetConfigResponse } from '../../../shared/search/models/facet-config-response.model';
|
||||||
import { FacetValues } from '../../../shared/search/facet-values.model';
|
import { FacetValues } from '../../../shared/search/models/facet-values.model';
|
||||||
import { SearchConfig } from './search-filters/search-config.model';
|
import { SearchConfig } from './search-filters/search-config.model';
|
||||||
import { PaginationService } from '../../pagination/pagination.service';
|
import { PaginationService } from '../../pagination/pagination.service';
|
||||||
import { SearchConfigurationService } from './search-configuration.service';
|
import { SearchConfigurationService } from './search-configuration.service';
|
||||||
@@ -407,6 +407,7 @@ export class SearchService implements OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Changes the current view mode in the current URL
|
* Changes the current view mode in the current URL
|
||||||
* @param {ViewMode} viewMode Mode to switch to
|
* @param {ViewMode} viewMode Mode to switch to
|
||||||
|
* @param {string[]} searchLinkParts
|
||||||
*/
|
*/
|
||||||
setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) {
|
setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) {
|
||||||
this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1))
|
this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1))
|
||||||
|
@@ -47,6 +47,12 @@ export class Version extends DSpaceObject {
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
summary: string;
|
summary: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the submitter of this version
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
submitterName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Date this version was created
|
* The Date this version was created
|
||||||
*/
|
*/
|
||||||
|
@@ -3,8 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export enum ViewMode {
|
export enum ViewMode {
|
||||||
ListElement = 'listElement',
|
ListElement = 'list',
|
||||||
GridElement = 'gridElement',
|
GridElement = 'grid',
|
||||||
DetailedListElement = 'detailedListElement',
|
DetailedListElement = 'detailed',
|
||||||
StandalonePage = 'standalonePage',
|
StandalonePage = 'standalone',
|
||||||
}
|
}
|
||||||
|
21
src/app/correlation-id/correlation-id.actions.ts
Normal file
21
src/app/correlation-id/correlation-id.actions.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { type } from '../shared/ngrx/type';
|
||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
export const CorrelationIDActionTypes = {
|
||||||
|
SET: type('dspace/core/correlationId/SET')
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action for setting a new correlation ID
|
||||||
|
*/
|
||||||
|
export class SetCorrelationIdAction implements Action {
|
||||||
|
type = CorrelationIDActionTypes.SET;
|
||||||
|
|
||||||
|
constructor(public payload: string) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type alias for all correlation ID actions
|
||||||
|
*/
|
||||||
|
export type CorrelationIdAction = SetCorrelationIdAction;
|
23
src/app/correlation-id/correlation-id.reducer.spec.ts
Normal file
23
src/app/correlation-id/correlation-id.reducer.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { correlationIdReducer } from './correlation-id.reducer';
|
||||||
|
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||||
|
|
||||||
|
describe('correlationIdReducer', () => {
|
||||||
|
it('should set the correlatinId with SET action', () => {
|
||||||
|
const initialState = null;
|
||||||
|
const currentState = correlationIdReducer(initialState, new SetCorrelationIdAction('new ID'));
|
||||||
|
|
||||||
|
expect(currentState).toBe('new ID');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should leave correlatinId unchanged otherwise', () => {
|
||||||
|
const initialState = null;
|
||||||
|
|
||||||
|
let currentState = correlationIdReducer(initialState, { type: 'unknown' } as any);
|
||||||
|
expect(currentState).toBe(null);
|
||||||
|
|
||||||
|
currentState = correlationIdReducer(currentState, new SetCorrelationIdAction('new ID'));
|
||||||
|
currentState = correlationIdReducer(currentState, { type: 'unknown' } as any);
|
||||||
|
|
||||||
|
expect(currentState).toBe('new ID');
|
||||||
|
});
|
||||||
|
});
|
27
src/app/correlation-id/correlation-id.reducer.ts
Normal file
27
src/app/correlation-id/correlation-id.reducer.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import {
|
||||||
|
CorrelationIdAction,
|
||||||
|
CorrelationIDActionTypes,
|
||||||
|
SetCorrelationIdAction
|
||||||
|
} from './correlation-id.actions';
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
|
|
||||||
|
const initialState = null;
|
||||||
|
|
||||||
|
export const correlationIdSelector = (state: AppState) => state.correlationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer that handles actions to update the correlation ID
|
||||||
|
* @param {string} state the previous correlation ID (null if unset)
|
||||||
|
* @param {CorrelationIdAction} action the action to perform
|
||||||
|
* @return {string} the new correlation ID
|
||||||
|
*/
|
||||||
|
export const correlationIdReducer = (state = initialState, action: CorrelationIdAction): string => {
|
||||||
|
switch (action.type) {
|
||||||
|
case CorrelationIDActionTypes.SET: {
|
||||||
|
return (action as SetCorrelationIdAction).payload;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
83
src/app/correlation-id/correlation-id.service.spec.ts
Normal file
83
src/app/correlation-id/correlation-id.service.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { CorrelationIdService } from './correlation-id.service';
|
||||||
|
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
|
||||||
|
import { UUIDService } from '../core/shared/uuid.service';
|
||||||
|
import { MockStore, provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { appReducers, AppState, storeModuleConfig } from '../app.reducer';
|
||||||
|
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||||
|
|
||||||
|
describe('CorrelationIdService', () => {
|
||||||
|
let service: CorrelationIdService;
|
||||||
|
|
||||||
|
let cookieService;
|
||||||
|
let uuidService;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cookieService = new CookieServiceMock();
|
||||||
|
uuidService = new UUIDService();
|
||||||
|
store = TestBed.inject(Store) as MockStore<AppState>;
|
||||||
|
service = new CorrelationIdService(cookieService, uuidService, store);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCorrelationId', () => {
|
||||||
|
it('should get from from store', () => {
|
||||||
|
expect(service.getCorrelationId()).toBe(null);
|
||||||
|
store.dispatch(new SetCorrelationIdAction('some value'));
|
||||||
|
expect(service.getCorrelationId()).toBe('some value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('initCorrelationId', () => {
|
||||||
|
const cookieCID = 'cookie CID';
|
||||||
|
const storeCID = 'store CID';
|
||||||
|
|
||||||
|
it('should set cookie and store values to a newly generated value if neither ex', () => {
|
||||||
|
service.initCorrelationId();
|
||||||
|
|
||||||
|
expect(cookieService.get('CORRELATION-ID')).toBeTruthy();
|
||||||
|
expect(service.getCorrelationId()).toBeTruthy();
|
||||||
|
expect(cookieService.get('CORRELATION-ID')).toEqual(service.getCorrelationId());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set store value to cookie value if present', () => {
|
||||||
|
expect(service.getCorrelationId()).toBe(null);
|
||||||
|
|
||||||
|
cookieService.set('CORRELATION-ID', cookieCID);
|
||||||
|
|
||||||
|
service.initCorrelationId();
|
||||||
|
|
||||||
|
expect(cookieService.get('CORRELATION-ID')).toBe(cookieCID);
|
||||||
|
expect(service.getCorrelationId()).toBe(cookieCID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set cookie value to store value if present', () => {
|
||||||
|
store.dispatch(new SetCorrelationIdAction(storeCID));
|
||||||
|
|
||||||
|
service.initCorrelationId();
|
||||||
|
|
||||||
|
expect(cookieService.get('CORRELATION-ID')).toBe(storeCID);
|
||||||
|
expect(service.getCorrelationId()).toBe(storeCID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set store value to cookie value if both are present', () => {
|
||||||
|
cookieService.set('CORRELATION-ID', cookieCID);
|
||||||
|
store.dispatch(new SetCorrelationIdAction(storeCID));
|
||||||
|
|
||||||
|
service.initCorrelationId();
|
||||||
|
|
||||||
|
expect(cookieService.get('CORRELATION-ID')).toBe(cookieCID);
|
||||||
|
expect(service.getCorrelationId()).toBe(cookieCID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
64
src/app/correlation-id/correlation-id.service.ts
Normal file
64
src/app/correlation-id/correlation-id.service.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { CookieService } from '../core/services/cookie.service';
|
||||||
|
import { UUIDService } from '../core/shared/uuid.service';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
|
import { isEmpty } from '../shared/empty.util';
|
||||||
|
import { correlationIdSelector } from './correlation-id.reducer';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { SetCorrelationIdAction } from './correlation-id.actions';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to manage the correlation id, an id used to give context to server side logs
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CorrelationIdService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected cookieService: CookieService,
|
||||||
|
protected uuidService: UUIDService,
|
||||||
|
protected store: Store<AppState>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the correlation id based on the cookie or the ngrx store
|
||||||
|
*/
|
||||||
|
initCorrelationId(): void {
|
||||||
|
// first see of there's a cookie with a correlation-id
|
||||||
|
let correlationId = this.cookieService.get('CORRELATION-ID');
|
||||||
|
|
||||||
|
// if there isn't see if there's an ID in the store
|
||||||
|
if (isEmpty(correlationId)) {
|
||||||
|
correlationId = this.getCorrelationId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no id was found, create a new id
|
||||||
|
if (isEmpty(correlationId)) {
|
||||||
|
correlationId = this.uuidService.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the correct id both in the store and as a cookie to ensure they're in sync
|
||||||
|
this.store.dispatch(new SetCorrelationIdAction(correlationId));
|
||||||
|
this.cookieService.set('CORRELATION-ID', correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the correlation id from the store
|
||||||
|
*/
|
||||||
|
getCorrelationId(): string {
|
||||||
|
let correlationId;
|
||||||
|
|
||||||
|
this.store.pipe(
|
||||||
|
select(correlationIdSelector),
|
||||||
|
take(1)
|
||||||
|
).subscribe((storeId: string) => {
|
||||||
|
// we can do this because ngrx selects are synchronous
|
||||||
|
correlationId = storeId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return correlationId;
|
||||||
|
}
|
||||||
|
}
|
@@ -19,6 +19,7 @@ import { JournalVolumeSearchResultGridElementComponent } from './item-grid-eleme
|
|||||||
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
|
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
|
||||||
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
|
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
|
||||||
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
|
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
|
||||||
|
import { ItemSharedModule } from '../../item-page/item-shared.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -45,6 +46,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
ItemSharedModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { OrgUnitComponent } from './item-pages/org-unit/org-unit.component';
|
import { OrgUnitComponent } from './item-pages/org-unit/org-unit.component';
|
||||||
import { PersonComponent } from './item-pages/person/person.component';
|
import { PersonComponent } from './item-pages/person/person.component';
|
||||||
@@ -27,6 +28,7 @@ import { ExternalSourceEntryListSubmissionElementComponent } from './submission/
|
|||||||
import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component';
|
import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component';
|
||||||
import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component';
|
import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component';
|
||||||
import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component';
|
import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component';
|
||||||
|
import { ItemSharedModule } from '../../item-page/item-shared.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -65,7 +67,9 @@ const COMPONENTS = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule
|
ItemSharedModule,
|
||||||
|
SharedModule,
|
||||||
|
NgbTooltipModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
@@ -79,7 +83,7 @@ export class ResearchEntitiesModule {
|
|||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
ngModule: ResearchEntitiesModule,
|
ngModule: ResearchEntitiesModule,
|
||||||
providers: ENTRY_COMPONENTS.map((component) => ({provide: component}))
|
providers: ENTRY_COMPONENTS.map((component) => ({ provide: component }))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
||||||
<div class="dropdown-list">
|
<div class="dropdown-list">
|
||||||
<div *ngFor="let suggestionOption of suggestions">
|
<div *ngFor="let suggestionOption of suggestions">
|
||||||
<a href="#" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
<a href="javascript:void(0);" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||||
<span [innerHTML]="suggestionOption"></span>
|
<span [innerHTML]="suggestionOption"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
||||||
<div class="dropdown-list">
|
<div class="dropdown-list">
|
||||||
<div *ngFor="let suggestionOption of suggestions">
|
<div *ngFor="let suggestionOption of suggestions">
|
||||||
<a href="#" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
<a href="javascript:void(0);" class="d-block dropdown-item" (click)="onClickSuggestion(suggestionOption)" #suggestion>
|
||||||
<span [innerHTML]="suggestionOption"></span>
|
<span [innerHTML]="suggestionOption"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -64,7 +64,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul class="footer-info list-unstyled small d-flex justify-content-center mb-0">
|
<ul class="footer-info list-unstyled small d-flex justify-content-center mb-0">
|
||||||
<li>
|
<li>
|
||||||
<a class="text-white" href="#"
|
<a class="text-white" href="javascript:void(0);"
|
||||||
(click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
(click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
|
import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
|
||||||
import { EditItemPageComponent } from './edit-item-page.component';
|
import { EditItemPageComponent } from './edit-item-page.component';
|
||||||
@@ -31,6 +34,8 @@ import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.co
|
|||||||
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 { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
||||||
|
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Item page administrator functionality
|
* Module that contains all components related to the Edit Item page administrator functionality
|
||||||
@@ -39,9 +44,11 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
NgbTooltipModule,
|
||||||
EditItemPageRoutingModule,
|
EditItemPageRoutingModule,
|
||||||
SearchPageModule,
|
SearchPageModule,
|
||||||
DragDropModule
|
DragDropModule,
|
||||||
|
ResourcePoliciesModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditItemPageComponent,
|
EditItemPageComponent,
|
||||||
|
@@ -15,14 +15,11 @@ import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core
|
|||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { Bundle } from '../../../core/shared/bundle.model';
|
import { Bundle } from '../../../core/shared/bundle.model';
|
||||||
import {
|
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
FieldUpdate,
|
|
||||||
FieldUpdates
|
|
||||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
|
||||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
||||||
import { BundleDataService } from '../../../core/data/bundle-data.service';
|
import { BundleDataService } from '../../../core/data/bundle-data.service';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes';
|
import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes';
|
||||||
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
|
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
@@ -5,7 +5,7 @@ import { Bitstream } from '../../../../../core/shared/bitstream.model';
|
|||||||
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
|
||||||
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
|
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../../../shared/search/models/paginated-search-options.model';
|
||||||
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||||
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
|
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { EventEmitter } from '@angular/core';
|
import { EventEmitter } from '@angular/core';
|
||||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
@@ -25,7 +25,7 @@ import { ObjectSelectService } from '../../../shared/object-select/object-select
|
|||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||||
import { SearchFormComponent } from '../../../shared/search-form/search-form.component';
|
import { SearchFormComponent } from '../../../shared/search-form/search-form.component';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service.stub';
|
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service.stub';
|
||||||
|
@@ -9,11 +9,12 @@ import { PaginatedList } from '../../../core/data/paginated-list.model';
|
|||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import {
|
import {
|
||||||
|
getAllSucceededRemoteData,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
getFirstSucceededRemoteData,
|
toDSpaceObjectListRD
|
||||||
toDSpaceObjectListRD,
|
|
||||||
getAllSucceededRemoteData, getFirstCompletedRemoteData
|
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
@@ -22,7 +23,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
@@ -3,7 +3,13 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
import { combineLatest as observableCombineLatest, from as observableFrom, BehaviorSubject, Observable, Subscription } from 'rxjs';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
from as observableFrom,
|
||||||
|
Observable,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
FieldUpdate,
|
FieldUpdate,
|
||||||
FieldUpdates,
|
FieldUpdates,
|
||||||
@@ -25,7 +31,7 @@ import { ItemType } from '../../../../core/shared/item-relationships/item-type.m
|
|||||||
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||||
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
||||||
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { SearchResult } from '../../../../shared/search/search-result.model';
|
import { SearchResult } from '../../../../shared/search/models/search-result.model';
|
||||||
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user