mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'dspace/master' into w2p-71174_FileSizePipe-refactor
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
"path": "./webpack/webpack.common.ts",
|
"path": "./webpack/webpack.common.ts",
|
||||||
"mergeStrategies": {
|
"mergeStrategies": {
|
||||||
"loaders": "prepend"
|
"loaders": "prepend"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"outputPath": "dist/browser",
|
"outputPath": "dist/browser",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
- 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.js
|
- environment.dev.ts
|
||||||
- Environment file for running DSpace Angular in Docker
|
- Environment file for running DSpace Angular in Docker
|
||||||
- local.cfg
|
- local.cfg
|
||||||
- Environment file for running the DSpace 7 REST API in Docker.
|
- Environment file for running the DSpace 7 REST API in Docker.
|
||||||
|
@@ -23,4 +23,4 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- ./environment.dev.js:/app/src/environments/environment.dev.ts
|
- ./environment.dev.ts:/app/src/environments/environment.dev.ts
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
/*
|
/**
|
||||||
* The contents of this file are subject to the license and copyright
|
* 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
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||||
* tree and available online at
|
* tree and available online at
|
||||||
*
|
*
|
||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
import { GlobalConfig } from '../src/config/global-config.interface';
|
// This file is based on environment.template.ts provided by Angular UI
|
||||||
|
export const environment = {
|
||||||
export const environment: Partial<GlobalConfig> = {
|
// Default to using the local REST API (running in Docker)
|
||||||
rest: {
|
rest: {
|
||||||
ssl: false,
|
ssl: false,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
dspace.dir=/dspace
|
dspace.dir=/dspace
|
||||||
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
||||||
dspace.server.url=http://localhost:8080/server
|
dspace.server.url=http://localhost:8080/server
|
||||||
|
dspace.ui.url=http://localhost:4000
|
||||||
dspace.name=DSpace Started with Docker Compose
|
dspace.name=DSpace Started with Docker Compose
|
||||||
solr.server=http://dspacesolr:8983/solr
|
solr.server=http://dspacesolr:8983/solr
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Default configuration file is located in `config/` folder. All configuration options should be listed in the default configuration file `config/environment.default.js`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change:
|
Default configuration file is located in `src/environments/` folder. All configuration options should be listed in the default configuration file `src/environments/environment.common.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `environment.template.ts` as a starting point.
|
||||||
|
|
||||||
- Create a new `environment.dev.js` file in `config/` for `devel` environment;
|
- Create a new `environment.dev.ts` file in `src/environments/` for `development` environment;
|
||||||
- Create a new `environment.prod.js` file in `config/` for `production` environment;
|
- Create a new `environment.prod.ts` file in `src/environments/` for `production` environment;
|
||||||
|
|
||||||
Some few configuration options can be overridden by setting environment variables. These and the variable names are listed below.
|
Some few configuration options can be overridden by setting environment variables. These and the variable names are listed below.
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ When you start dspace-angular on node, it spins up an http server on which it li
|
|||||||
|
|
||||||
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
||||||
```
|
```
|
||||||
module.exports = {
|
export const environment = {
|
||||||
// Angular Universal server settings.
|
// Angular UI settings.
|
||||||
ui: {
|
ui: {
|
||||||
ssl: false,
|
ssl: false,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
@@ -35,14 +35,14 @@ Alternately you can set the following environment variables. If any of these are
|
|||||||
dspace-angular connects to your DSpace installation by using its REST endpoint. To do so, you have to define the ip address, port and if ssl should be enabled. You can do this in a configuration file (see above) by adding the following options:
|
dspace-angular connects to your DSpace installation by using its REST endpoint. To do so, you have to define the ip address, port and if ssl should be enabled. You can do this in a configuration file (see above) by adding the following options:
|
||||||
|
|
||||||
```
|
```
|
||||||
module.exports = {
|
export const environment = {
|
||||||
// The REST API server settings.
|
// The REST API server settings.
|
||||||
rest: {
|
rest: {
|
||||||
ssl: true,
|
ssl: true,
|
||||||
host: 'dspace7.4science.it',
|
host: 'dspace7.4science.cloud',
|
||||||
port: 443,
|
port: 443,
|
||||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||||
nameSpace: '/dspace-spring-rest/api'
|
nameSpace: '/server/api'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -50,9 +50,9 @@ module.exports = {
|
|||||||
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
|
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
|
||||||
```
|
```
|
||||||
DSPACE_REST_SSL=true
|
DSPACE_REST_SSL=true
|
||||||
DSPACE_REST_HOST=localhost
|
DSPACE_REST_HOST=dspace7.4science.cloud
|
||||||
DSPACE_REST_PORT=4000
|
DSPACE_REST_PORT=443
|
||||||
DSPACE_REST_NAMESPACE=/
|
DSPACE_REST_NAMESPACE=/server/api
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supporting analytics services other than Google Analytics
|
## Supporting analytics services other than Google Analytics
|
||||||
|
11
package.json
11
package.json
@@ -17,7 +17,7 @@
|
|||||||
"pree2e": "yarn run config:prod",
|
"pree2e": "yarn run config:prod",
|
||||||
"pree2e:ci": "yarn run config:prod",
|
"pree2e:ci": "yarn run config:prod",
|
||||||
"start": "yarn run start:prod",
|
"start": "yarn run start:prod",
|
||||||
"serve": "ng serve",
|
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||||
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
||||||
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
@@ -52,6 +52,9 @@
|
|||||||
"https": false
|
"https": false
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"resolutions": {
|
||||||
|
"minimist": "^1.2.5"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~8.2.14",
|
"@angular/animations": "~8.2.14",
|
||||||
"@angular/cdk": "8.2.3",
|
"@angular/cdk": "8.2.3",
|
||||||
@@ -147,7 +150,7 @@
|
|||||||
"jasmine-core": "^3.3.0",
|
"jasmine-core": "^3.3.0",
|
||||||
"jasmine-marbles": "0.3.1",
|
"jasmine-marbles": "0.3.1",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"karma": "~4.1.0",
|
"karma": "^5.0.9",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||||
"karma-jasmine": "2.0.1",
|
"karma-jasmine": "2.0.1",
|
||||||
@@ -158,10 +161,10 @@
|
|||||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
"postcss-apply": "0.11.0",
|
"postcss-apply": "0.11.0",
|
||||||
"postcss-cssnext": "3.1.0",
|
"postcss-cssnext": "3.1.0",
|
||||||
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-responsive-type": "1.0.0",
|
"postcss-responsive-type": "1.0.0",
|
||||||
"postcss-smart-import": "^0.7.6",
|
"protractor": "^7.0.0",
|
||||||
"protractor": "~5.4.0",
|
|
||||||
"protractor-istanbul-plugin": "2.0.0",
|
"protractor-istanbul-plugin": "2.0.0",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
require('postcss-smart-import')(),
|
require('postcss-import')(),
|
||||||
require('postcss-cssnext')(),
|
require('postcss-cssnext')(),
|
||||||
require('postcss-apply')(),
|
require('postcss-apply')(),
|
||||||
require('postcss-responsive-type')()
|
require('postcss-responsive-type')()
|
||||||
|
11
scripts/serve.ts
Normal file
11
scripts/serve.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { environment } from '../src/environments/environment';
|
||||||
|
|
||||||
|
import * as child from 'child_process';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls `ng serve` with the following arguments configured for the UI in the environment file: host, port, nameSpace, ssl
|
||||||
|
*/
|
||||||
|
child.spawn(
|
||||||
|
`ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`,
|
||||||
|
{ stdio:'inherit', shell: true }
|
||||||
|
);
|
211
server.ts
211
server.ts
@@ -17,26 +17,67 @@
|
|||||||
|
|
||||||
import 'zone.js/dist/zone-node';
|
import 'zone.js/dist/zone-node';
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
import 'rxjs';
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as pem from 'pem';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as morgan from 'morgan';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import { join } from 'path';
|
|
||||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
|
||||||
import * as bodyParser from 'body-parser';
|
import * as bodyParser from 'body-parser';
|
||||||
|
import * as compression from 'compression';
|
||||||
import * as cookieParser from 'cookie-parser';
|
import * as cookieParser from 'cookie-parser';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { enableProdMode, NgModuleFactory, Type } from '@angular/core';
|
||||||
|
|
||||||
|
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||||
import { environment } from './src/environments/environment';
|
import { environment } from './src/environments/environment';
|
||||||
|
|
||||||
// Express server
|
/*
|
||||||
const app = express();
|
* Set path for the browser application's dist folder
|
||||||
|
*/
|
||||||
const PORT = environment.ui.port || 4000;
|
|
||||||
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
|
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
|
||||||
|
|
||||||
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
|
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
|
||||||
const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main');
|
const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new express application
|
||||||
|
*/
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If production mode is enabled in the environment file:
|
||||||
|
* - Enable Angular's production mode
|
||||||
|
* - Enable compression for response bodies. See [compression](https://github.com/expressjs/compression)
|
||||||
|
*/
|
||||||
|
if (environment.production) {
|
||||||
|
enableProdMode();
|
||||||
|
app.use(compression());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable request logging
|
||||||
|
* See [morgan](https://github.com/expressjs/morgan)
|
||||||
|
*/
|
||||||
|
app.use(morgan('dev'));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add cookie parser middleware
|
||||||
|
* See [morgan](https://github.com/expressjs/cookie-parser)
|
||||||
|
*/
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add parser for request bodies
|
||||||
|
* See [morgan](https://github.com/expressjs/body-parser)
|
||||||
|
*/
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Render html pages by running angular server side
|
||||||
|
*/
|
||||||
app.engine('html', (_, options, callback) =>
|
app.engine('html', (_, options, callback) =>
|
||||||
ngExpressEngine({
|
ngExpressEngine({
|
||||||
bootstrap: ServerAppModuleNgFactory,
|
bootstrap: ServerAppModuleNgFactory,
|
||||||
@@ -51,25 +92,155 @@ app.engine('html', (_, options, callback) =>
|
|||||||
},
|
},
|
||||||
provideModuleMap(LAZY_MODULE_MAP)
|
provideModuleMap(LAZY_MODULE_MAP)
|
||||||
],
|
],
|
||||||
})(_, options, callback)
|
})(_, (options as any), callback)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register the view engines for html and ejs
|
||||||
|
*/
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
app.set('view engine', 'html');
|
app.set('view engine', 'html');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set views folder path to directory where template files are stored
|
||||||
|
*/
|
||||||
app.set('views', DIST_FOLDER);
|
app.set('views', DIST_FOLDER);
|
||||||
|
|
||||||
// Example Express Rest API endpoints
|
/*
|
||||||
// app.get('/api/**', (req, res) => { });
|
* Adds a cache control header to the response
|
||||||
// Serve static files from /browser
|
* The cache control value can be configured in the environments file and defaults to max-age=60
|
||||||
app.get('*.*', express.static(DIST_FOLDER, {
|
*/
|
||||||
maxAge: '1y'
|
function cacheControl(req, res, next) {
|
||||||
}));
|
// instruct browser to revalidate
|
||||||
|
res.header('Cache-Control', environment.cache.control || 'max-age=60');
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
// All regular routes use the Universal engine
|
/*
|
||||||
app.get('*', (req, res) => {
|
* Serve static resources (images, i18n messages, …)
|
||||||
res.render('index', { req });
|
*/
|
||||||
});
|
app.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false }));
|
||||||
|
|
||||||
// Start up the Node server
|
/*
|
||||||
app.listen(PORT, () => {
|
* The callback function to serve server side angular
|
||||||
console.log(`Node Express server listening on http://localhost:${PORT}`);
|
*/
|
||||||
|
function ngApp(req, res) {
|
||||||
|
// Object to be set to window.dspace when CSR is used
|
||||||
|
// this allows us to pass the info in the original request
|
||||||
|
// to the dspace7-angular instance running in the client's browser
|
||||||
|
const dspace = {
|
||||||
|
originalRequest: {
|
||||||
|
headers: req.headers,
|
||||||
|
body: req.body,
|
||||||
|
method: req.method,
|
||||||
|
params: req.params,
|
||||||
|
reportProgress: req.reportProgress,
|
||||||
|
withCredentials: req.withCredentials,
|
||||||
|
responseType: req.responseType,
|
||||||
|
urlWithParams: req.urlWithParams
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback function for the case when SSR throws an error.
|
||||||
|
function onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
|
||||||
|
if (!res._headerSent) {
|
||||||
|
console.warn('Error in SSR, serving for direct CSR. Error details : ', error);
|
||||||
|
res.sendFile('index.csr.ejs', {
|
||||||
|
root: DIST_FOLDER,
|
||||||
|
scripts: `<script>window.dspace = ${JSON.stringify(dspace)}</script>`
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment.universal.preboot) {
|
||||||
|
// If preboot is enabled, create a new zone for SSR, and
|
||||||
|
// register the error handler for when it throws an error
|
||||||
|
Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => {
|
||||||
|
res.render(DIST_FOLDER + '/index.html', {
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
preboot: environment.universal.preboot,
|
||||||
|
async: environment.universal.async,
|
||||||
|
time: environment.universal.time,
|
||||||
|
baseUrl: environment.ui.nameSpace,
|
||||||
|
originUrl: environment.ui.baseUrl,
|
||||||
|
requestUrl: req.originalUrl
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If preboot is disabled, just serve the client side ejs template and pass it the required
|
||||||
|
// variables
|
||||||
|
console.log('Universal off, serving for direct CSR');
|
||||||
|
res.render('index-csr.ejs', {
|
||||||
|
root: DIST_FOLDER,
|
||||||
|
scripts: `<script>window.dspace = ${JSON.stringify(dspace)}</script>`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the ngApp callback function to handle incoming requests
|
||||||
|
app.get('*', ngApp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function for when the server has started
|
||||||
|
*/
|
||||||
|
function serverStarted() {
|
||||||
|
console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an HTTPS server with the configured port and host
|
||||||
|
* @param keys SSL credentials
|
||||||
|
*/
|
||||||
|
function createHttpsServer(keys) {
|
||||||
|
https.createServer({
|
||||||
|
key: keys.serviceKey,
|
||||||
|
cert: keys.certificate
|
||||||
|
}, app).listen(environment.ui.port, environment.ui.host, () => {
|
||||||
|
serverStarted();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If SSL is enabled
|
||||||
|
* - Read credentials from configuration files
|
||||||
|
* - Call script to start an HTTPS server with these credentials
|
||||||
|
* When SSL is disabled
|
||||||
|
* - Start an HTTP server on the configured port and host
|
||||||
|
*/
|
||||||
|
if (environment.ui.ssl) {
|
||||||
|
let serviceKey;
|
||||||
|
try {
|
||||||
|
serviceKey = fs.readFileSync('./config/ssl/key.pem');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Service key not found at ./config/ssl/key.pem');
|
||||||
|
}
|
||||||
|
|
||||||
|
let certificate;
|
||||||
|
try {
|
||||||
|
certificate = fs.readFileSync('./config/ssl/cert.pem');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Certificate not found at ./config/ssl/key.pem');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceKey && certificate) {
|
||||||
|
createHttpsServer({
|
||||||
|
serviceKey: serviceKey,
|
||||||
|
certificate: certificate
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
|
|
||||||
|
pem.createCertificate({
|
||||||
|
days: 1,
|
||||||
|
selfSigned: true
|
||||||
|
}, (error, keys) => {
|
||||||
|
createHttpsServer(keys);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.listen(environment.ui.port, environment.ui.host, () => {
|
||||||
|
serverStarted();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon
|
|||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { getAccessControlModulePath } from '../admin-routing.module';
|
import { getAccessControlModulePath } from '../admin-routing.module';
|
||||||
|
|
||||||
const GROUP_EDIT_PATH = 'groups';
|
export const GROUP_EDIT_PATH = 'groups';
|
||||||
|
|
||||||
export function getGroupEditPath(id: string) {
|
export function getGroupEditPath(id: string) {
|
||||||
return new URLCombiner(getAccessControlModulePath(), GROUP_EDIT_PATH, id).toString();
|
return new URLCombiner(getAccessControlModulePath(), GROUP_EDIT_PATH, id).toString();
|
||||||
|
@@ -8,7 +8,7 @@ import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.ser
|
|||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
const REGISTRIES_MODULE_PATH = 'registries';
|
const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||||
|
|
||||||
export function getRegistriesModulePath() {
|
export function getRegistriesModulePath() {
|
||||||
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
||||||
|
@@ -29,6 +29,9 @@ import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edi
|
|||||||
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
||||||
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
||||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||||
|
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -67,6 +70,9 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version
|
|||||||
ItemMoveComponent,
|
ItemMoveComponent,
|
||||||
ItemEditBitstreamDragHandleComponent,
|
ItemEditBitstreamDragHandleComponent,
|
||||||
VirtualMetadataComponent,
|
VirtualMetadataComponent,
|
||||||
|
ItemAuthorizationsComponent,
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BundleDataService
|
BundleDataService
|
||||||
|
@@ -14,6 +14,12 @@ import { ItemMoveComponent } from './item-move/item-move.component';
|
|||||||
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
||||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
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 { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
|
|
||||||
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||||
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||||
@@ -21,6 +27,7 @@ export const ITEM_EDIT_PRIVATE_PATH = 'private';
|
|||||||
export const ITEM_EDIT_PUBLIC_PATH = 'public';
|
export const ITEM_EDIT_PUBLIC_PATH = 'public';
|
||||||
export const ITEM_EDIT_DELETE_PATH = 'delete';
|
export const ITEM_EDIT_DELETE_PATH = 'delete';
|
||||||
export const ITEM_EDIT_MOVE_PATH = 'move';
|
export const ITEM_EDIT_MOVE_PATH = 'move';
|
||||||
|
export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing module that handles the routing for the Edit Item page administrator functionality
|
* Routing module that handles the routing for the Edit Item page administrator functionality
|
||||||
@@ -111,12 +118,43 @@ export const ITEM_EDIT_MOVE_PATH = 'move';
|
|||||||
path: ITEM_EDIT_MOVE_PATH,
|
path: ITEM_EDIT_MOVE_PATH,
|
||||||
component: ItemMoveComponent,
|
component: ItemMoveComponent,
|
||||||
data: { title: 'item.edit.move.title' },
|
data: { title: 'item.edit.move.title' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicyTarget: ResourcePolicyTargetResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyCreateComponent,
|
||||||
|
data: { title: 'resource-policies.create.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicy: ResourcePolicyResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyEditComponent,
|
||||||
|
data: { title: 'resource-policies.edit.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ItemAuthorizationsComponent,
|
||||||
|
data: { title: 'item.edit.authorizations.title' }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: []
|
providers: [
|
||||||
|
I18nBreadcrumbResolver,
|
||||||
|
I18nBreadcrumbsService,
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class EditItemPageRoutingModule {
|
export class EditItemPageRoutingModule {
|
||||||
|
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
||||||
|
<ds-resource-policies [resourceType]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
|
||||||
|
<ng-container *ngFor="let bundle of (getItemBundles() | async); trackById">
|
||||||
|
<ds-resource-policies [resourceType]="'bundle'"
|
||||||
|
[resourceUUID]="bundle.id"></ds-resource-policies>
|
||||||
|
<ng-container *ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id) | async)?.page; trackById">
|
||||||
|
<ds-resource-policies [resourceType]="'bitstream'"
|
||||||
|
[resourceUUID]="bitstream.id"></ds-resource-policies>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
@@ -0,0 +1,183 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { ItemAuthorizationsComponent } from './item-authorizations.component';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
import { Bundle } from '../../../core/shared/bundle.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { getMockLinkService } from '../../../shared/mocks/link-service.mock';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
|
||||||
|
describe('ItemAuthorizationsComponent test suite', () => {
|
||||||
|
let comp: ItemAuthorizationsComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ItemAuthorizationsComponent>;
|
||||||
|
let de;
|
||||||
|
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
|
||||||
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream1',
|
||||||
|
uuid: 'bitstream1'
|
||||||
|
});
|
||||||
|
const bitstream2 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream2',
|
||||||
|
uuid: 'bitstream2'
|
||||||
|
});
|
||||||
|
const bitstream3 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream3',
|
||||||
|
uuid: 'bitstream3'
|
||||||
|
});
|
||||||
|
const bitstream4 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream4',
|
||||||
|
uuid: 'bitstream4'
|
||||||
|
});
|
||||||
|
const bundle1 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle1',
|
||||||
|
uuid: 'bundle1',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle1-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
|
||||||
|
});
|
||||||
|
const bundle2 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle2',
|
||||||
|
uuid: 'bundle2',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle2-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4])
|
||||||
|
});
|
||||||
|
const bundles = [bundle1, bundle2];
|
||||||
|
const bitstreamList1: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream1, bitstream2]);
|
||||||
|
const bitstreamList2: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream3, bitstream4]);
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'item',
|
||||||
|
id: 'item',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([bundle1, bundle2])
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
NoopAnimationsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ItemAuthorizationsComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
ItemAuthorizationsComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-item-authorizations></ds-item-authorizations>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ItemAuthorizationsComponent', inject([ItemAuthorizationsComponent], (app: ItemAuthorizationsComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemAuthorizationsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init bundles and bitstreams map properly', () => {
|
||||||
|
expect(compAsAny.subs.length).toBe(2);
|
||||||
|
expect(compAsAny.bundles$.value).toEqual(bundles);
|
||||||
|
expect(compAsAny.bundleBitstreamsMap.has('bundle1')).toBeTruthy();
|
||||||
|
expect(compAsAny.bundleBitstreamsMap.has('bundle2')).toBeTruthy();
|
||||||
|
let bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle1');
|
||||||
|
expect(bitstreamList).toBeObservable(cold('(a|)', {
|
||||||
|
a: bitstreamList1
|
||||||
|
}));
|
||||||
|
|
||||||
|
bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle2');
|
||||||
|
expect(bitstreamList).toBeObservable(cold('(a|)', {
|
||||||
|
a: bitstreamList2
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the item UUID', () => {
|
||||||
|
|
||||||
|
expect(comp.getItemUUID()).toBeObservable(cold('(a|)', {
|
||||||
|
a: item.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the item\'s bundle', () => {
|
||||||
|
|
||||||
|
expect(comp.getItemBundles()).toBeObservable(cold('a', {
|
||||||
|
a: bundles
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,155 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { catchError, filter, first, flatMap, map, take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import {
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload
|
||||||
|
} from '../../../core/shared/operators';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { Bundle } from '../../../core/shared/bundle.model';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a bundle's bitstream map entry
|
||||||
|
*/
|
||||||
|
interface BundleBitstreamsMapEntry {
|
||||||
|
id: string;
|
||||||
|
bitstreams: Observable<PaginatedList<Bitstream>>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-authorizations',
|
||||||
|
templateUrl: './item-authorizations.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that handles the item Authorizations
|
||||||
|
*/
|
||||||
|
export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map that contains all bitstream of the item's bundles
|
||||||
|
* @type {Observable<Map<string, Observable<PaginatedList<Bitstream>>>>}
|
||||||
|
*/
|
||||||
|
public bundleBitstreamsMap: Map<string, Observable<PaginatedList<Bitstream>>> = new Map<string, Observable<PaginatedList<Bitstream>>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of bundle for the item
|
||||||
|
* @type {Observable<PaginatedList<Bundle>>}
|
||||||
|
*/
|
||||||
|
private bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target editing item
|
||||||
|
* @type {Observable<Item>}
|
||||||
|
*/
|
||||||
|
private item$: Observable<Item>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {LinkService} linkService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private linkService: LinkService,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the bundle and bitstream within the item
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.item$ = this.route.data.pipe(
|
||||||
|
map((data) => data.item),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((item: Item) => this.linkService.resolveLink(
|
||||||
|
item,
|
||||||
|
followLink('bundles', new FindListOptions(), true, followLink('bitstreams'))
|
||||||
|
))
|
||||||
|
) as Observable<Item>;
|
||||||
|
|
||||||
|
const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
|
||||||
|
filter((item: Item) => isNotEmpty(item.bundles)),
|
||||||
|
flatMap((item: Item) => item.bundles),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return observableOf(new PaginatedList(null, []))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subs.push(
|
||||||
|
bundles$.pipe(
|
||||||
|
take(1),
|
||||||
|
map((list: PaginatedList<Bundle>) => list.page)
|
||||||
|
).subscribe((bundles: Bundle[]) => {
|
||||||
|
this.bundles$.next(bundles);
|
||||||
|
}),
|
||||||
|
bundles$.pipe(
|
||||||
|
take(1),
|
||||||
|
flatMap((list: PaginatedList<Bundle>) => list.page),
|
||||||
|
map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) }))
|
||||||
|
).subscribe((entry: BundleBitstreamsMapEntry) => {
|
||||||
|
this.bundleBitstreamsMap.set(entry.id, entry.bitstreams)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the item's UUID
|
||||||
|
*/
|
||||||
|
getItemUUID(): Observable<string> {
|
||||||
|
return this.item$.pipe(
|
||||||
|
map((item: Item) => item.id),
|
||||||
|
first((UUID: string) => isNotEmpty(UUID))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all item's bundles
|
||||||
|
*
|
||||||
|
* @return an observable that emits all item's bundles
|
||||||
|
*/
|
||||||
|
getItemBundles(): Observable<Bundle[]> {
|
||||||
|
return this.bundles$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all bundle's bitstreams
|
||||||
|
*
|
||||||
|
* @return an observable that emits all item's bundles
|
||||||
|
*/
|
||||||
|
private getBundleBitstreams(bundle: Bundle): Observable<PaginatedList<Bitstream>> {
|
||||||
|
return bundle.bitstreams.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return observableOf(new PaginatedList(null, []))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
}
|
@@ -68,6 +68,7 @@ export class ItemStatusComponent implements OnInit {
|
|||||||
The value is supposed to be a href for the button
|
The value is supposed to be a href for the button
|
||||||
*/
|
*/
|
||||||
this.operations = [];
|
this.operations = [];
|
||||||
|
this.operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations'));
|
||||||
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
|
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
|
||||||
if (item.isWithdrawn) {
|
if (item.isWithdrawn) {
|
||||||
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
|
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
|
||||||
|
@@ -7,6 +7,7 @@ import { Item } from '../core/shared/item.model';
|
|||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { find } from 'rxjs/operators';
|
import { find } from 'rxjs/operators';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||||
|
import { FindListOptions } from '../core/data/request.models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
@@ -26,7 +27,7 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
|||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||||
return this.itemService.findById(route.params.id,
|
return this.itemService.findById(route.params.id,
|
||||||
followLink('owningCollection'),
|
followLink('owningCollection'),
|
||||||
followLink('bundles'),
|
followLink('bundles', new FindListOptions(), true, followLink('bitstreams')),
|
||||||
followLink('relationships'),
|
followLink('relationships'),
|
||||||
followLink('version', undefined, true, followLink('versionhistory')),
|
followLink('version', undefined, true, followLink('versionhistory')),
|
||||||
).pipe(
|
).pipe(
|
||||||
|
@@ -33,7 +33,7 @@ export function getBitstreamModulePath() {
|
|||||||
return `/${BITSTREAM_MODULE_PATH}`;
|
return `/${BITSTREAM_MODULE_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ADMIN_MODULE_PATH = 'admin';
|
export const ADMIN_MODULE_PATH = 'admin';
|
||||||
|
|
||||||
export function getAdminModulePath() {
|
export function getAdminModulePath() {
|
||||||
return `/${ADMIN_MODULE_PATH}`;
|
return `/${ADMIN_MODULE_PATH}`;
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
||||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<ng-container *ngTemplateOutlet="breadcrumbs.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'Home', url: '/'}"></ng-container>
|
<ng-container
|
||||||
|
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'Home', url: '/'}"></ng-container>
|
||||||
<ng-container *ngFor="let bc of breadcrumbs; let last = last;">
|
<ng-container *ngFor="let bc of breadcrumbs; let last = last;">
|
||||||
<ng-container *ngTemplateOutlet="!last ? breadcrumb : activeBreadcrumb; context: bc"></ng-container>
|
<ng-container *ngTemplateOutlet="!last ? breadcrumb : activeBreadcrumb; context: bc"></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -14,4 +16,5 @@
|
|||||||
<ng-template #activeBreadcrumb let-text="text">
|
<ng-template #activeBreadcrumb let-text="text">
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{text | translate}}</li>
|
<li class="breadcrumb-item active" aria-current="page">{{text | translate}}</li>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
@@ -9,6 +9,9 @@ import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
|
|||||||
import { BreadcrumbConfig } from './breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from './breadcrumb/breadcrumb-config.model';
|
||||||
import { BreadcrumbsService } from '../core/breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from '../core/breadcrumbs/breadcrumbs.service';
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
|
||||||
class TestBreadcrumbsService implements BreadcrumbsService<string> {
|
class TestBreadcrumbsService implements BreadcrumbsService<string> {
|
||||||
@@ -64,17 +67,16 @@ describe('BreadcrumbsComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [BreadcrumbsComponent],
|
declarations: [BreadcrumbsComponent, VarDirective],
|
||||||
imports: [RouterTestingModule.withRoutes([]), TranslateModule.forRoot({
|
imports: [RouterTestingModule.withRoutes([]), TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
useClass: TranslateLoaderMock
|
useClass: TranslateLoaderMock
|
||||||
}
|
}
|
||||||
})],
|
}), NgbModule],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ActivatedRoute, useValue: route}
|
{provide: ActivatedRoute, useValue: route}
|
||||||
|
], schemas: [NO_ERRORS_SCHEMA]
|
||||||
]
|
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -92,14 +94,16 @@ describe('BreadcrumbsComponent', () => {
|
|||||||
|
|
||||||
describe('ngOnInit', () => {
|
describe('ngOnInit', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]))
|
spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call resolveBreadcrumb on init', () => {
|
it('should call resolveBreadcrumb on init', () => {
|
||||||
router.events = observableOf(new NavigationEnd(0, '', ''));
|
router.events = observableOf(new NavigationEnd(0, '', ''));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('resolveBreadcrumbs', () => {
|
describe('resolveBreadcrumbs', () => {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
import { hasNoValue, hasValue, isNotUndefined, isUndefined } from '../shared/empty.util';
|
import { hasNoValue, hasValue, isUndefined } from '../shared/empty.util';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { combineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
|
import { combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the breadcrumbs of a page
|
* Component representing the breadcrumbs of a page
|
||||||
@@ -13,22 +13,17 @@ import { combineLatest, Observable, Subscription, of as observableOf } from 'rxj
|
|||||||
templateUrl: './breadcrumbs.component.html',
|
templateUrl: './breadcrumbs.component.html',
|
||||||
styleUrls: ['./breadcrumbs.component.scss']
|
styleUrls: ['./breadcrumbs.component.scss']
|
||||||
})
|
})
|
||||||
export class BreadcrumbsComponent implements OnInit, OnDestroy {
|
export class BreadcrumbsComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* List of breadcrumbs for this page
|
* Observable of the list of breadcrumbs for this page
|
||||||
*/
|
*/
|
||||||
breadcrumbs: Breadcrumb[];
|
breadcrumbs$: Observable<Breadcrumb[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to show breadcrumbs on this page
|
* Whether or not to show breadcrumbs on this page
|
||||||
*/
|
*/
|
||||||
showBreadcrumbs: boolean;
|
showBreadcrumbs: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription to unsubscribe from on destroy
|
|
||||||
*/
|
|
||||||
subscription: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router
|
||||||
@@ -39,14 +34,11 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
|
|||||||
* Sets the breadcrumbs on init for this page
|
* Sets the breadcrumbs on init for this page
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.subscription = this.router.events.pipe(
|
this.breadcrumbs$ = this.router.events.pipe(
|
||||||
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
||||||
tap(() => this.reset()),
|
tap(() => this.reset()),
|
||||||
switchMap(() => this.resolveBreadcrumbs(this.route.root))
|
switchMap(() => this.resolveBreadcrumbs(this.route.root)),
|
||||||
).subscribe((breadcrumbs) => {
|
);
|
||||||
this.breadcrumbs = breadcrumbs;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,20 +73,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
|
|||||||
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from subscription
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (hasValue(this.subscription)) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the state of the breadcrumbs
|
* Resets the state of the breadcrumbs
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
this.breadcrumbs = [];
|
|
||||||
this.showBreadcrumbs = true;
|
this.showBreadcrumbs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { fakeAsync, flush, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { provideMockActions } from '@ngrx/effects/testing';
|
import { provideMockActions } from '@ngrx/effects/testing';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { MockStore, provideMockStore } from '@ngrx/store/testing';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
|
|
||||||
import { Observable, of as observableOf, throwError as observableThrow } from 'rxjs';
|
import { Observable, of as observableOf, throwError as observableThrow } from 'rxjs';
|
||||||
|
|
||||||
import { AuthEffects } from './auth.effects';
|
import { AuthEffects } from './auth.effects';
|
||||||
@@ -29,41 +29,53 @@ import {
|
|||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
import { authMethodsMock, AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
import { authMethodsMock, AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthState } from './auth.reducer';
|
import { authReducer } from './auth.reducer';
|
||||||
|
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { EPersonMock } from '../../shared/testing/eperson.mock';
|
import { EPersonMock } from '../../shared/testing/eperson.mock';
|
||||||
|
import { AppState, storeModuleConfig } from '../../app.reducer';
|
||||||
|
import { StoreActionTypes } from '../../store.actions';
|
||||||
|
import { isAuthenticated, isAuthenticatedLoaded } from './selectors';
|
||||||
|
|
||||||
describe('AuthEffects', () => {
|
describe('AuthEffects', () => {
|
||||||
let authEffects: AuthEffects;
|
let authEffects: AuthEffects;
|
||||||
let actions: Observable<any>;
|
let actions: Observable<any>;
|
||||||
let authServiceStub;
|
let authServiceStub;
|
||||||
const store: Store<AuthState> = jasmine.createSpyObj('store', {
|
let initialState;
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
dispatch: {},
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
select: observableOf(true)
|
|
||||||
});
|
|
||||||
let token;
|
let token;
|
||||||
|
let store: MockStore<AppState>;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
authServiceStub = new AuthServiceStub();
|
authServiceStub = new AuthServiceStub();
|
||||||
token = authServiceStub.getToken();
|
token = authServiceStub.getToken();
|
||||||
|
initialState = {
|
||||||
|
core: {
|
||||||
|
auth: {
|
||||||
|
authenticated: false,
|
||||||
|
loaded: false,
|
||||||
|
loading: false,
|
||||||
|
authMethods: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StoreModule.forRoot({ auth: authReducer }, storeModuleConfig)
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthEffects,
|
AuthEffects,
|
||||||
|
provideMockStore({ initialState }),
|
||||||
{ provide: AuthService, useValue: authServiceStub },
|
{ provide: AuthService, useValue: authServiceStub },
|
||||||
{ provide: Store, useValue: store },
|
|
||||||
provideMockActions(() => actions),
|
provideMockActions(() => actions),
|
||||||
// other providers
|
// other providers
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
authEffects = TestBed.get(AuthEffects);
|
authEffects = TestBed.get(AuthEffects);
|
||||||
|
store = TestBed.get(Store);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('authenticate$', () => {
|
describe('authenticate$', () => {
|
||||||
@@ -362,4 +374,40 @@ describe('AuthEffects', () => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('clearInvalidTokenOnRehydrate$', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store.overrideSelector(isAuthenticated, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when auth loaded is false', () => {
|
||||||
|
it('should not call removeToken method', (done) => {
|
||||||
|
store.overrideSelector(isAuthenticatedLoaded, false);
|
||||||
|
actions = hot('--a-|', { a: { type: StoreActionTypes.REHYDRATE } });
|
||||||
|
spyOn(authServiceStub, 'removeToken');
|
||||||
|
|
||||||
|
authEffects.clearInvalidTokenOnRehydrate$.subscribe(() => {
|
||||||
|
expect(authServiceStub.removeToken).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when auth loaded is true', () => {
|
||||||
|
it('should call removeToken method', fakeAsync(() => {
|
||||||
|
store.overrideSelector(isAuthenticatedLoaded, true);
|
||||||
|
actions = hot('--a-|', { a: { type: StoreActionTypes.REHYDRATE } });
|
||||||
|
spyOn(authServiceStub, 'removeToken');
|
||||||
|
|
||||||
|
authEffects.clearInvalidTokenOnRehydrate$.subscribe(() => {
|
||||||
|
expect(authServiceStub.removeToken).toHaveBeenCalled();
|
||||||
|
flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,20 +1,18 @@
|
|||||||
import { Observable, of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
import { catchError, debounceTime, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { catchError, debounceTime, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||||
// import @ngrx
|
// import @ngrx
|
||||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
import { Action, select, Store } from '@ngrx/store';
|
import { Action, select, Store } from '@ngrx/store';
|
||||||
|
|
||||||
// import services
|
// import services
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
import { EPerson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { isAuthenticated } from './selectors';
|
import { isAuthenticated, isAuthenticatedLoaded } from './selectors';
|
||||||
import { StoreActionTypes } from '../../store.actions';
|
import { StoreActionTypes } from '../../store.actions';
|
||||||
import { AuthMethod } from './models/auth.method';
|
import { AuthMethod } from './models/auth.method';
|
||||||
// import actions
|
// import actions
|
||||||
@@ -187,10 +185,11 @@ export class AuthEffects {
|
|||||||
public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$.pipe(
|
public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$.pipe(
|
||||||
ofType(StoreActionTypes.REHYDRATE),
|
ofType(StoreActionTypes.REHYDRATE),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
return this.store.pipe(
|
const isLoaded$ = this.store.pipe(select(isAuthenticatedLoaded));
|
||||||
select(isAuthenticated),
|
const authenticated$ = this.store.pipe(select(isAuthenticated));
|
||||||
|
return observableCombineLatest(isLoaded$, authenticated$).pipe(
|
||||||
take(1),
|
take(1),
|
||||||
filter((authenticated) => !authenticated),
|
filter(([loaded, authenticated]) => loaded && !authenticated),
|
||||||
tap(() => this.authService.removeToken()),
|
tap(() => this.authService.removeToken()),
|
||||||
tap(() => this.authService.resetAuthenticationError())
|
tap(() => this.authService.resetAuthenticationError())
|
||||||
);
|
);
|
||||||
|
@@ -48,7 +48,7 @@ describe(`AuthInterceptor`, () => {
|
|||||||
|
|
||||||
describe('when has a valid token', () => {
|
describe('when has a valid token', () => {
|
||||||
|
|
||||||
it('should not add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
it('should not add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint that is not the logout endpoint', () => {
|
||||||
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/authn/login', 'password=password&user=user').subscribe((response) => {
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/authn/login', 'password=password&user=user').subscribe((response) => {
|
||||||
expect(response).toBeTruthy();
|
expect(response).toBeTruthy();
|
||||||
});
|
});
|
||||||
@@ -58,8 +58,19 @@ describe(`AuthInterceptor`, () => {
|
|||||||
const token = httpRequest.request.headers.get('authorization');
|
const token = httpRequest.request.headers.get('authorization');
|
||||||
expect(token).toBeNull();
|
expect(token).toBeNull();
|
||||||
});
|
});
|
||||||
|
it('should add an Authorization header when we’re sending a HTTP request to the\'authn/logout\' endpoint', () => {
|
||||||
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/authn/logout', 'test').subscribe((response) => {
|
||||||
|
expect(response).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
const httpRequest = httpMock.expectOne(`dspace-spring-rest/api/authn/logout`);
|
||||||
|
|
||||||
|
expect(httpRequest.request.headers.has('authorization'));
|
||||||
|
const token = httpRequest.request.headers.get('authorization');
|
||||||
|
expect(token).toBe('Bearer token_test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add an Authorization header when we’re sending a HTTP request to a non-\'authn\' endpoint', () => {
|
||||||
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/submission/workspaceitems', 'test').subscribe((response) => {
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/submission/workspaceitems', 'test').subscribe((response) => {
|
||||||
expect(response).toBeTruthy();
|
expect(response).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@@ -221,7 +221,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
// Redirect to the login route
|
// Redirect to the login route
|
||||||
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
|
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
|
||||||
return observableOf(null);
|
return observableOf(null);
|
||||||
} else if (!this.isAuthRequest(req) && isNotEmpty(token)) {
|
} else if ((!this.isAuthRequest(req) || this.isLogoutResponse(req)) && isNotEmpty(token)) {
|
||||||
// Intercept a request that is not to the authentication endpoint
|
// Intercept a request that is not to the authentication endpoint
|
||||||
authService.isTokenExpiring().pipe(
|
authService.isTokenExpiring().pipe(
|
||||||
filter((isExpiring) => isExpiring))
|
filter((isExpiring) => isExpiring))
|
||||||
|
@@ -8,7 +8,9 @@ import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-con
|
|||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for a Collection
|
* The class that resolves the BreadcrumbConfig object for a Collection
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {
|
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CollectionDataService) {
|
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CollectionDataService) {
|
||||||
super(breadcrumbService, dataService);
|
super(breadcrumbService, dataService);
|
||||||
|
@@ -8,7 +8,9 @@ import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-con
|
|||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for a Community
|
* The class that resolves the BreadcrumbConfig object for a Community
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {
|
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CommunityDataService) {
|
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CommunityDataService) {
|
||||||
super(breadcrumbService, dataService);
|
super(breadcrumbService, dataService);
|
||||||
|
@@ -13,7 +13,9 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
|
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> implements Resolve<BreadcrumbConfig<T>> {
|
export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> implements Resolve<BreadcrumbConfig<T>> {
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: DataService<T>) {
|
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: DataService<T>) {
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,9 @@ import { Injectable } from '@angular/core';
|
|||||||
/**
|
/**
|
||||||
* Service to calculate DSpaceObject breadcrumbs for a single part of the route
|
* Service to calculate DSpaceObject breadcrumbs for a single part of the route
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResource & DSpaceObject> {
|
export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResource & DSpaceObject> {
|
||||||
constructor(
|
constructor(
|
||||||
private linkService: LinkService,
|
private linkService: LinkService,
|
||||||
|
@@ -28,7 +28,8 @@ export class DSONameService {
|
|||||||
return dso.firstMetadataValue('organization.legalName');
|
return dso.firstMetadataValue('organization.legalName');
|
||||||
},
|
},
|
||||||
Default: (dso: DSpaceObject): string => {
|
Default: (dso: DSpaceObject): string => {
|
||||||
return dso.firstMetadataValue('dc.title');
|
// If object doesn't have dc.title metadata use name property
|
||||||
|
return dso.firstMetadataValue('dc.title') || dso.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -7,7 +7,9 @@ import { hasNoValue } from '../../shared/empty.util';
|
|||||||
/**
|
/**
|
||||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
|
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class I18nBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {
|
export class I18nBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {
|
||||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,9 @@ export const BREADCRUMB_MESSAGE_POSTFIX = '.breadcrumbs';
|
|||||||
/**
|
/**
|
||||||
* Service to calculate i18n breadcrumbs for a single part of the route
|
* Service to calculate i18n breadcrumbs for a single part of the route
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class I18nBreadcrumbsService implements BreadcrumbsService<string> {
|
export class I18nBreadcrumbsService implements BreadcrumbsService<string> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -8,7 +8,9 @@ import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-con
|
|||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for an Item
|
* The class that resolves the BreadcrumbConfig object for an Item
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: ItemDataService) {
|
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: ItemDataService) {
|
||||||
super(breadcrumbService, dataService);
|
super(breadcrumbService, dataService);
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object
|
* Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object
|
||||||
*/
|
*/
|
||||||
export class SearchParam {
|
export class RequestParam {
|
||||||
constructor(public fieldName: string, public fieldValue: any) {
|
constructor(public fieldName: string, public fieldValue: any) {
|
||||||
|
|
||||||
}
|
}
|
@@ -78,7 +78,7 @@ import { RegistryMetadatafieldsResponseParsingService } from './data/registry-me
|
|||||||
import { RegistryMetadataschemasResponseParsingService } from './data/registry-metadataschemas-response-parsing.service';
|
import { RegistryMetadataschemasResponseParsingService } from './data/registry-metadataschemas-response-parsing.service';
|
||||||
import { RelationshipTypeService } from './data/relationship-type.service';
|
import { RelationshipTypeService } from './data/relationship-type.service';
|
||||||
import { RelationshipService } from './data/relationship.service';
|
import { RelationshipService } from './data/relationship.service';
|
||||||
import { ResourcePolicyService } from './data/resource-policy.service';
|
import { ResourcePolicyService } from './resource-policy/resource-policy.service';
|
||||||
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
||||||
import { SiteDataService } from './data/site-data.service';
|
import { SiteDataService } from './data/site-data.service';
|
||||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||||
@@ -116,7 +116,7 @@ import { RelationshipType } from './shared/item-relationships/relationship-type.
|
|||||||
import { Relationship } from './shared/item-relationships/relationship.model';
|
import { Relationship } from './shared/item-relationships/relationship.model';
|
||||||
import { Item } from './shared/item.model';
|
import { Item } from './shared/item.model';
|
||||||
import { License } from './shared/license.model';
|
import { License } from './shared/license.model';
|
||||||
import { ResourcePolicy } from './shared/resource-policy.model';
|
import { ResourcePolicy } from './resource-policy/models/resource-policy.model';
|
||||||
import { SearchConfigurationService } from './shared/search/search-configuration.service';
|
import { SearchConfigurationService } from './shared/search/search-configuration.service';
|
||||||
import { SearchFilterService } from './shared/search/search-filter.service';
|
import { SearchFilterService } from './shared/search/search-filter.service';
|
||||||
import { SearchService } from './shared/search/search.service';
|
import { SearchService } from './shared/search/search.service';
|
||||||
|
@@ -12,7 +12,7 @@ import { PaginatedSearchOptions } from '../../shared/search/paginated-search-opt
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
|
import { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
@@ -94,7 +94,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
const searchHref = 'findAuthorizedByCommunity';
|
const searchHref = 'findAuthorizedByCommunity';
|
||||||
options = Object.assign({}, options, {
|
options = Object.assign({}, options, {
|
||||||
searchParams: [new SearchParam('uuid', communityId)]
|
searchParams: [new RequestParam('uuid', communityId)]
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
|
@@ -20,7 +20,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { getClassForType } from '../cache/builders/build-decorators';
|
import { getClassForType } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||||
@@ -111,7 +111,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
result$ = this.getSearchEndpoint(searchMethod);
|
result$ = this.getSearchEndpoint(searchMethod);
|
||||||
|
|
||||||
if (hasValue(options.searchParams)) {
|
if (hasValue(options.searchParams)) {
|
||||||
options.searchParams.forEach((param: SearchParam) => {
|
options.searchParams.forEach((param: RequestParam) => {
|
||||||
args.push(`${param.fieldName}=${param.fieldValue}`);
|
args.push(`${param.fieldName}=${param.fieldValue}`);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -153,6 +153,33 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn an array of RequestParam into a query string and combine it with the given HREF
|
||||||
|
*
|
||||||
|
* @param href The HREF to which the query string should be appended
|
||||||
|
* @param params Array with additional params to combine with query string
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*
|
||||||
|
* @return {Observable<string>}
|
||||||
|
* Return an observable that emits created HREF
|
||||||
|
*/
|
||||||
|
protected buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: Array<FollowLinkConfig<T>>): string {
|
||||||
|
|
||||||
|
let args = [];
|
||||||
|
if (hasValue(params)) {
|
||||||
|
params.forEach((param: RequestParam) => {
|
||||||
|
args.push(`${param.fieldName}=${param.fieldValue}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
args = this.addEmbedParams(args, ...linksToFollow);
|
||||||
|
|
||||||
|
if (isNotEmpty(args)) {
|
||||||
|
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
|
} else {
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Adds the embed options to the link for the request
|
* Adds the embed options to the link for the request
|
||||||
* @param args params for the query string
|
* @param args params for the query string
|
||||||
@@ -293,9 +320,9 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
* @param searchMethod The search method for the object
|
* @param searchMethod The search method for the object
|
||||||
*/
|
*/
|
||||||
protected getSearchEndpoint(searchMethod: string): Observable<string> {
|
protected getSearchEndpoint(searchMethod: string): Observable<string> {
|
||||||
return this.halService.getEndpoint(`${this.linkPath}/search`).pipe(
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
map((href: string) => `${href}/${searchMethod}`));
|
map((href: string) => `${href}/search/${searchMethod}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -380,15 +407,15 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
*
|
*
|
||||||
* @param {DSpaceObject} dso
|
* @param {DSpaceObject} dso
|
||||||
* The object to create
|
* The object to create
|
||||||
* @param {string} parentUUID
|
* @param {RequestParam[]} params
|
||||||
* The UUID of the parent to create the new object under
|
* Array with additional params to combine with query string
|
||||||
*/
|
*/
|
||||||
create(dso: T, parentUUID: string): Observable<RemoteData<T>> {
|
create(dso: T, ...params: RequestParam[]): Observable<RemoteData<T>> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe(
|
const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
isNotEmptyOperator(),
|
isNotEmptyOperator(),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint)
|
map((endpoint: string) => this.buildHrefWithParams(endpoint, params))
|
||||||
);
|
);
|
||||||
|
|
||||||
const serializedDso = new DSpaceSerializer(getClassForType((dso as any).type)).serialize(dso);
|
const serializedDso = new DSpaceSerializer(getClassForType((dso as any).type)).serialize(dso);
|
||||||
@@ -479,7 +506,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
const requestId = this.deleteAndReturnRequestId(dsoID, copyVirtualMetadata);
|
const requestId = this.deleteAndReturnRequestId(dsoID, copyVirtualMetadata);
|
||||||
|
|
||||||
return this.requestService.getByUUID(requestId).pipe(
|
return this.requestService.getByUUID(requestId).pipe(
|
||||||
find((request: RequestEntry) => request.completed),
|
find((request: RequestEntry) => isNotEmpty(request) && request.completed),
|
||||||
map((request: RequestEntry) => request.response.isSuccessful)
|
map((request: RequestEntry) => request.response.isSuccessful)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
@@ -257,7 +257,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
}
|
}
|
||||||
const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
|
const searchParams = [new RequestParam('label', label), new RequestParam('dso', item.id)];
|
||||||
if (findListOptions.searchParams) {
|
if (findListOptions.searchParams) {
|
||||||
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -11,7 +11,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
|||||||
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
||||||
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
||||||
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
|
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
|
||||||
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
||||||
@@ -146,7 +146,7 @@ export class FindListOptions {
|
|||||||
elementsPerPage?: number;
|
elementsPerPage?: number;
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
sort?: SortOptions;
|
sort?: SortOptions;
|
||||||
searchParams?: SearchParam[];
|
searchParams?: RequestParam[];
|
||||||
startsWith?: string;
|
startsWith?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { ResourcePolicy } from '../shared/resource-policy.model';
|
|
||||||
import { RequestService } from './request.service';
|
|
||||||
import { ResourcePolicyService } from './resource-policy.service';
|
|
||||||
|
|
||||||
describe('ResourcePolicyService', () => {
|
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let service: ResourcePolicyService;
|
|
||||||
let requestService: RequestService;
|
|
||||||
let rdbService: RemoteDataBuildService;
|
|
||||||
let objectCache: ObjectCacheService;
|
|
||||||
const testObject = {
|
|
||||||
uuid: '664184ee-b254-45e8-970d-220e5ccc060b'
|
|
||||||
} as ResourcePolicy;
|
|
||||||
const requestURL = `https://rest.api/rest/api/resourcepolicies/${testObject.uuid}`;
|
|
||||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
|
|
||||||
requestService = jasmine.createSpyObj('requestService', {
|
|
||||||
generateRequestId: requestUUID,
|
|
||||||
configure: true
|
|
||||||
});
|
|
||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
|
||||||
buildSingle: cold('a', {
|
|
||||||
a: {
|
|
||||||
payload: testObject
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
objectCache = {} as ObjectCacheService;
|
|
||||||
const halService = {} as HALEndpointService;
|
|
||||||
const notificationsService = {} as NotificationsService;
|
|
||||||
const http = {} as HttpClient;
|
|
||||||
const comparator = {} as any;
|
|
||||||
|
|
||||||
service = new ResourcePolicyService(
|
|
||||||
requestService,
|
|
||||||
rdbService,
|
|
||||||
objectCache,
|
|
||||||
halService,
|
|
||||||
notificationsService,
|
|
||||||
http,
|
|
||||||
comparator
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findByHref', () => {
|
|
||||||
it('should proxy the call to dataservice.findByHref', () => {
|
|
||||||
scheduler.schedule(() => service.findByHref(requestURL));
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(requestURL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a RemoteData<ResourcePolicy> for the object with the given URL', () => {
|
|
||||||
const result = service.findByHref(requestURL);
|
|
||||||
const expected = cold('a', {
|
|
||||||
a: {
|
|
||||||
payload: testObject
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(result).toBeObservable(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,95 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
|
||||||
|
|
||||||
import { DataService } from '../data/data.service';
|
|
||||||
import { RequestService } from '../data/request.service';
|
|
||||||
import { FindListOptions } from '../data/request.models';
|
|
||||||
import { Collection } from '../shared/collection.model';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { ResourcePolicy } from '../shared/resource-policy.model';
|
|
||||||
import { RemoteData } from '../data/remote-data';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { RESOURCE_POLICY } from '../shared/resource-policy.resource-type';
|
|
||||||
import { ChangeAnalyzer } from './change-analyzer';
|
|
||||||
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
|
||||||
import { PaginatedList } from './paginated-list';
|
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A private DataService implementation to delegate specific methods to.
|
|
||||||
*/
|
|
||||||
class DataServiceImpl extends DataService<ResourcePolicy> {
|
|
||||||
protected linkPath = 'resourcepolicies';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected requestService: RequestService,
|
|
||||||
protected rdbService: RemoteDataBuildService,
|
|
||||||
protected store: Store<CoreState>,
|
|
||||||
protected objectCache: ObjectCacheService,
|
|
||||||
protected halService: HALEndpointService,
|
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
protected http: HttpClient,
|
|
||||||
protected comparator: ChangeAnalyzer<ResourcePolicy>) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service responsible for fetching/sending data from/to the REST API on the resourcepolicies endpoint
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
@dataService(RESOURCE_POLICY)
|
|
||||||
export class ResourcePolicyService {
|
|
||||||
private dataService: DataServiceImpl;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected requestService: RequestService,
|
|
||||||
protected rdbService: RemoteDataBuildService,
|
|
||||||
protected objectCache: ObjectCacheService,
|
|
||||||
protected halService: HALEndpointService,
|
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
protected http: HttpClient,
|
|
||||||
protected comparator: DefaultChangeAnalyzer<ResourcePolicy>) {
|
|
||||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on an href, with a list of {@link FollowLinkConfig},
|
|
||||||
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
|
||||||
* @param href The url of {@link ResourcePolicy} we want to retrieve
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
|
||||||
*/
|
|
||||||
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
|
||||||
return this.dataService.findByHref(href, ...linksToFollow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of observables of {@link RemoteData} of {@link ResourcePolicy}s, based on an href, with a list of {@link FollowLinkConfig},
|
|
||||||
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
|
||||||
* @param href The url of the {@link ResourcePolicy} we want to retrieve
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
|
||||||
*/
|
|
||||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
|
||||||
return this.dataService.findAllByHref(href, findListOptions, ...linksToFollow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the defaultAccessConditions {@link ResourcePolicy} list for a given {@link Collection}
|
|
||||||
*
|
|
||||||
* @param collection the {@link Collection} to retrieve the defaultAccessConditions for
|
|
||||||
* @param findListOptions the {@link FindListOptions} for the request
|
|
||||||
*/
|
|
||||||
getDefaultAccessConditionsFor(collection: Collection, findListOptions?: FindListOptions): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
|
||||||
return this.dataService.findAllByHref(collection._links.defaultAccessConditions.href, findListOptions);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -9,7 +9,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
|
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -105,7 +105,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search by default scope (byMetadata) and no query', () => {
|
it('search by default scope (byMetadata) and no query', () => {
|
||||||
service.searchByScope(null, '');
|
service.searchByScope(null, '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -113,7 +113,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search metadata scope and no query', () => {
|
it('search metadata scope and no query', () => {
|
||||||
service.searchByScope('metadata', '');
|
service.searchByScope('metadata', '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -121,7 +121,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search metadata scope and with query', () => {
|
it('search metadata scope and with query', () => {
|
||||||
service.searchByScope('metadata', 'test');
|
service.searchByScope('metadata', 'test');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', 'test'))]
|
searchParams: [Object.assign(new RequestParam('query', 'test'))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -129,7 +129,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search email scope and no query', () => {
|
it('search email scope and no query', () => {
|
||||||
service.searchByScope('email', '');
|
service.searchByScope('email', '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('email', ''))]
|
searchParams: [Object.assign(new RequestParam('email', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byEmail', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byEmail', options);
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
@@ -97,7 +97,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
const searchParams = [new SearchParam('email', query)];
|
const searchParams = [new RequestParam('email', query)];
|
||||||
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow);
|
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
const searchParams = [new SearchParam('query', query)];
|
const searchParams = [new RequestParam('query', query)];
|
||||||
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow);
|
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param options
|
* @param options
|
||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEPeopleBy(searchParams: SearchParam[], searchMethod: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEPeopleBy(searchParams: RequestParam[], searchMethod: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
let findListOptions = new FindListOptions();
|
let findListOptions = new FindListOptions();
|
||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
|
@@ -11,7 +11,7 @@ import {
|
|||||||
GroupRegistryEditGroupAction
|
GroupRegistryEditGroupAction
|
||||||
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
||||||
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
|
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -103,7 +103,7 @@ describe('GroupDataService', () => {
|
|||||||
it('search with empty query', () => {
|
it('search with empty query', () => {
|
||||||
service.searchGroups('');
|
service.searchGroups('');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -111,7 +111,7 @@ describe('GroupDataService', () => {
|
|||||||
it('search with query', () => {
|
it('search with query', () => {
|
||||||
service.searchGroups('test');
|
service.searchGroups('test');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', 'test'))]
|
searchParams: [Object.assign(new RequestParam('query', 'test'))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
|
@@ -14,7 +14,7 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
@@ -97,7 +97,7 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
public searchGroups(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
public searchGroups(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
const searchParams = [new SearchParam('query', query)];
|
const searchParams = [new RequestParam('query', query)];
|
||||||
let findListOptions = new FindListOptions();
|
let findListOptions = new FindListOptions();
|
||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
@@ -121,7 +121,7 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
isMemberOf(groupName: string): Observable<boolean> {
|
isMemberOf(groupName: string): Observable<boolean> {
|
||||||
const searchHref = 'isMemberOf';
|
const searchHref = 'isMemberOf';
|
||||||
const options = new FindListOptions();
|
const options = new FindListOptions();
|
||||||
options.searchParams = [new SearchParam('groupName', groupName)];
|
options.searchParams = [new RequestParam('groupName', groupName)];
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
||||||
|
@@ -5,27 +5,27 @@ export enum ActionType {
|
|||||||
/**
|
/**
|
||||||
* Action of reading, viewing or downloading something
|
* Action of reading, viewing or downloading something
|
||||||
*/
|
*/
|
||||||
READ = 0,
|
READ = 'READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of modifying something
|
* Action of modifying something
|
||||||
*/
|
*/
|
||||||
WRITE = 1,
|
WRITE = 'WRITE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of deleting something
|
* Action of deleting something
|
||||||
*/
|
*/
|
||||||
DELETE = 2,
|
DELETE = 'DELETE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of adding something to a container
|
* Action of adding something to a container
|
||||||
*/
|
*/
|
||||||
ADD = 3,
|
ADD = 'ADD',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of removing something from a container
|
* Action of removing something from a container
|
||||||
*/
|
*/
|
||||||
REMOVE = 4,
|
REMOVE = 'REMOVE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of performing workflow step 1
|
* Action of performing workflow step 1
|
||||||
@@ -50,15 +50,20 @@ export enum ActionType {
|
|||||||
/**
|
/**
|
||||||
* Default Read policies for Bitstreams submitted to container
|
* Default Read policies for Bitstreams submitted to container
|
||||||
*/
|
*/
|
||||||
DEFAULT_BITSTREAM_READ = 9,
|
DEFAULT_BITSTREAM_READ = 'DEFAULT_BITSTREAM_READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Read policies for Items submitted to container
|
* Default Read policies for Items submitted to container
|
||||||
*/
|
*/
|
||||||
DEFAULT_ITEM_READ = 10,
|
DEFAULT_ITEM_READ = 'DEFAULT_ITEM_READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Administrative actions
|
* Administrative actions
|
||||||
*/
|
*/
|
||||||
ADMIN = 11,
|
ADMIN = 'ADMIN',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action of withdrawn reading
|
||||||
|
*/
|
||||||
|
WITHDRAWN_READ = 'WITHDRAWN_READ'
|
||||||
}
|
}
|
25
src/app/core/resource-policy/models/policy-type.model.ts
Normal file
25
src/app/core/resource-policy/models/policy-type.model.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Enum representing the Policy Type of a Resource Policy
|
||||||
|
*/
|
||||||
|
export enum PolicyType {
|
||||||
|
/**
|
||||||
|
* A policy in place during the submission
|
||||||
|
*/
|
||||||
|
TYPE_SUBMISSION = 'TYPE_SUBMISSION',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy in place during the approval workflow
|
||||||
|
*/
|
||||||
|
TYPE_WORKFLOW = 'TYPE_WORKFLOW',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy that has been inherited from a container (the collection)
|
||||||
|
*/
|
||||||
|
TYPE_INHERITED = 'TYPE_INHERITED',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy defined by the user during the submission or workflow phase
|
||||||
|
*/
|
||||||
|
TYPE_CUSTOM = 'TYPE_CUSTOM',
|
||||||
|
|
||||||
|
}
|
105
src/app/core/resource-policy/models/resource-policy.model.ts
Normal file
105
src/app/core/resource-policy/models/resource-policy.model.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
|
||||||
|
import { link, typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
|
||||||
|
import { ActionType } from './action-type.model';
|
||||||
|
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { PolicyType } from './policy-type.model';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { GROUP } from '../../eperson/models/group.resource-type';
|
||||||
|
import { Group } from '../../eperson/models/group.model';
|
||||||
|
import { EPERSON } from '../../eperson/models/eperson.resource-type';
|
||||||
|
import { EPerson } from '../../eperson/models/eperson.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for a Resource Policy
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class ResourcePolicy implements CacheableObject {
|
||||||
|
static type = RESOURCE_POLICY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The classification or this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
policyType: PolicyType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action that is allowed by this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
action: ActionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first day of validity of the policy (format YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
startDate: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last day of validity of the policy (format YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
endDate: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The universally unique identifier for this Resource Policy
|
||||||
|
* This UUID is generated client-side and isn't used by the backend.
|
||||||
|
* It is based on the ID, so it will be the same for each refresh.
|
||||||
|
*/
|
||||||
|
@deserializeAs(new IDToUUIDSerializer('resource-policy'), 'id')
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HALLink}s for this ResourcePolicy
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
eperson: HALLink,
|
||||||
|
group: HALLink,
|
||||||
|
self: HALLink,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson linked by this resource policy
|
||||||
|
* Will be undefined unless the version {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(EPERSON)
|
||||||
|
eperson?: Observable<RemoteData<EPerson>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The group linked by this resource policy
|
||||||
|
* Will be undefined unless the version {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(GROUP)
|
||||||
|
group?: Observable<RemoteData<Group>>;
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resource type for ResourcePolicy
|
* The resource type for ResourcePolicy
|
||||||
@@ -6,4 +6,4 @@ import { ResourceType } from './resource-type';
|
|||||||
* Needs to be in a separate file to prevent circular
|
* Needs to be in a separate file to prevent circular
|
||||||
* dependencies in webpack.
|
* dependencies in webpack.
|
||||||
*/
|
*/
|
||||||
export const RESOURCE_POLICY = new ResourceType('resourcePolicy');
|
export const RESOURCE_POLICY = new ResourceType('resourcepolicy');
|
319
src/app/core/resource-policy/resource-policy.service.spec.ts
Normal file
319
src/app/core/resource-policy/resource-policy.service.spec.ts
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { ResourcePolicyService } from './resource-policy.service';
|
||||||
|
import { PolicyType } from './models/policy-type.model';
|
||||||
|
import { ActionType } from './models/action-type.model';
|
||||||
|
import { FindListOptions } from '../data/request.models';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
|
||||||
|
describe('ResourcePolicyService', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: ResourcePolicyService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const anotherResourcePolicy: any = {
|
||||||
|
id: '2',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-2',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const endpointURL = `https://rest.api/rest/api/resourcepolicies`;
|
||||||
|
const requestURL = `https://rest.api/rest/api/resourcepolicies/${resourcePolicy.id}`;
|
||||||
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
const resourcePolicyId = '1';
|
||||||
|
const epersonUUID = '8b39g7ya-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
const groupUUID = '8b39g7ya-5a4b-36987-9686-be1d5b4a1c5a';
|
||||||
|
const resourceUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a';
|
||||||
|
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
const array = [resourcePolicy, anotherResourcePolicy];
|
||||||
|
const paginatedList = new PaginatedList(pageInfo, array);
|
||||||
|
const resourcePolicyRD = createSuccessfulRemoteDataObject(resourcePolicy);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: cold('a', { a: endpointURL })
|
||||||
|
});
|
||||||
|
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
configure: true,
|
||||||
|
removeByHrefSubstring: {},
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
|
getByUUID: observableOf(responseCacheEntry),
|
||||||
|
});
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: hot('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
}),
|
||||||
|
buildList: hot('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
const notificationsService = {} as NotificationsService;
|
||||||
|
const http = {} as HttpClient;
|
||||||
|
const comparator = {} as any;
|
||||||
|
|
||||||
|
service = new ResourcePolicyService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparator
|
||||||
|
);
|
||||||
|
|
||||||
|
spyOn((service as any).dataService, 'create').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'delete').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'update').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'findById').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'searchBy').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'getSearchByHref').and.returnValue(observableOf(requestURL));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should proxy the call to dataservice.create with eperson UUID', () => {
|
||||||
|
scheduler.schedule(() => service.create(resourcePolicy, resourceUUID, epersonUUID));
|
||||||
|
const params = [
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
new RequestParam('eperson', epersonUUID)
|
||||||
|
];
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.create).toHaveBeenCalledWith(resourcePolicy, ...params);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.create with group UUID', () => {
|
||||||
|
scheduler.schedule(() => service.create(resourcePolicy, resourceUUID, null, groupUUID));
|
||||||
|
const params = [
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
new RequestParam('group', groupUUID)
|
||||||
|
];
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.create).toHaveBeenCalledWith(resourcePolicy, ...params);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given id', () => {
|
||||||
|
const result = service.create(resourcePolicy, resourceUUID, epersonUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should proxy the call to dataservice.create', () => {
|
||||||
|
scheduler.schedule(() => service.delete(resourcePolicyId));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.delete).toHaveBeenCalledWith(resourcePolicyId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should proxy the call to dataservice.update', () => {
|
||||||
|
scheduler.schedule(() => service.update(resourcePolicy));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.update).toHaveBeenCalledWith(resourcePolicy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findById', () => {
|
||||||
|
it('should proxy the call to dataservice.findById', () => {
|
||||||
|
scheduler.schedule(() => service.findById(resourcePolicyId));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.findById).toHaveBeenCalledWith(resourcePolicyId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given id', () => {
|
||||||
|
const result = service.findById(resourcePolicyId);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findByHref', () => {
|
||||||
|
it('should proxy the call to dataservice.findByHref', () => {
|
||||||
|
scheduler.schedule(() => service.findByHref(requestURL));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(requestURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given URL', () => {
|
||||||
|
const result = service.findByHref(requestURL);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByEPerson', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', epersonUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByEPerson(epersonUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByEPersonMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', epersonUUID),
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByEPerson(epersonUUID, resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByEPersonMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByEPerson(epersonUUID, resourceUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByGroup', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', groupUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByGroup(groupUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', groupUUID),
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByGroup(groupUUID, resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByGroup(groupUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByResource', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', resourceUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByResource(resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByResourceMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const action = ActionType.READ;
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', resourceUUID),
|
||||||
|
new RequestParam('action', action),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByResource(resourceUUID, action));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByResourceMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByResource(resourceUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
193
src/app/core/resource-policy/resource-policy.service.ts
Normal file
193
src/app/core/resource-policy/resource-policy.service.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
|
||||||
|
import { DataService } from '../data/data.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { FindListOptions } from '../data/request.models';
|
||||||
|
import { Collection } from '../shared/collection.model';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ResourcePolicy } from './models/resource-policy.model';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { RESOURCE_POLICY } from './models/resource-policy.resource-type';
|
||||||
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
|
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { ActionType } from './models/action-type.model';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private DataService implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
class DataServiceImpl extends DataService<ResourcePolicy> {
|
||||||
|
protected linkPath = 'resourcepolicies';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: ChangeAnalyzer<ResourcePolicy>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service responsible for fetching/sending data from/to the REST API on the resourcepolicies endpoint
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(RESOURCE_POLICY)
|
||||||
|
export class ResourcePolicyService {
|
||||||
|
private dataService: DataServiceImpl;
|
||||||
|
protected searchByEPersonMethod = 'eperson';
|
||||||
|
protected searchByGroupMethod = 'group';
|
||||||
|
protected searchByResourceMethod = 'resource';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<ResourcePolicy>) {
|
||||||
|
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ResourcePolicy on the server, and store the response
|
||||||
|
* in the object cache
|
||||||
|
*
|
||||||
|
* @param {ResourcePolicy} resourcePolicy
|
||||||
|
* The resource policy to create
|
||||||
|
* @param {string} resourceUUID
|
||||||
|
* The uuid of the resource target of the policy
|
||||||
|
* @param {string} epersonUUID
|
||||||
|
* The uuid of the eperson that will be grant of the permission. Exactly one of eperson or group is required
|
||||||
|
* @param {string} groupUUID
|
||||||
|
* The uuid of the group that will be grant of the permission. Exactly one of eperson or group is required
|
||||||
|
*/
|
||||||
|
create(resourcePolicy: ResourcePolicy, resourceUUID: string, epersonUUID?: string, groupUUID?: string): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
const params = [];
|
||||||
|
params.push(new RequestParam('resource', resourceUUID));
|
||||||
|
if (isNotEmpty(epersonUUID)) {
|
||||||
|
params.push(new RequestParam('eperson', epersonUUID));
|
||||||
|
} else if (isNotEmpty(groupUUID)) {
|
||||||
|
params.push(new RequestParam('group', groupUUID));
|
||||||
|
}
|
||||||
|
return this.dataService.create(resourcePolicy, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing ResourcePolicy on the server
|
||||||
|
*
|
||||||
|
* @param resourcePolicyID The resource policy's id to be removed
|
||||||
|
* @return an observable that emits true when the deletion was successful, false when it failed
|
||||||
|
*/
|
||||||
|
delete(resourcePolicyID: string): Observable<boolean> {
|
||||||
|
return this.dataService.delete(resourcePolicyID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new patch to the object cache
|
||||||
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
|
* @param {ResourcePolicy} object The given object
|
||||||
|
*/
|
||||||
|
update(object: ResourcePolicy): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.update(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on an href, with a list of {@link FollowLinkConfig},
|
||||||
|
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
||||||
|
* @param href The url of {@link ResourcePolicy} we want to retrieve
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.findByHref(href, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on its ID, with a list of {@link FollowLinkConfig},
|
||||||
|
* to automatically resolve {@link HALLink}s of the object
|
||||||
|
* @param id ID of {@link ResourcePolicy} we want to retrieve
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findById(id: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.findById(id, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the defaultAccessConditions {@link ResourcePolicy} list for a given {@link Collection}
|
||||||
|
*
|
||||||
|
* @param collection the {@link Collection} to retrieve the defaultAccessConditions for
|
||||||
|
* @param findListOptions the {@link FindListOptions} for the request
|
||||||
|
*/
|
||||||
|
getDefaultAccessConditionsFor(collection: Collection, findListOptions?: FindListOptions): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
return this.dataService.findAllByHref(collection._links.defaultAccessConditions.href, findListOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a {@link EPerson}
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given {@link EPerson}
|
||||||
|
* @param resourceUUID Limit the returned policies to the specified DSO
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByEPerson(UUID: string, resourceUUID?: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(resourceUUID)) {
|
||||||
|
options.searchParams.push(new RequestParam('resource', resourceUUID))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByEPersonMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a {@link Group}
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given {@link Group}
|
||||||
|
* @param resourceUUID Limit the returned policies to the specified DSO
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByGroup(UUID: string, resourceUUID?: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(resourceUUID)) {
|
||||||
|
options.searchParams.push(new RequestParam('resource', resourceUUID))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByGroupMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a given DSO
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given DSO
|
||||||
|
* @param action Limit the returned policies to the specified {@link ActionType}
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByResource(UUID: string, action?: ActionType, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(action)) {
|
||||||
|
options.searchParams.push(new RequestParam('action', action))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByResourceMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,8 +1,15 @@
|
|||||||
import { deserialize, inheritSerialization } from 'cerialize';
|
import { deserialize, inheritSerialization } from 'cerialize';
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||||
import { BUNDLE } from './bundle.resource-type';
|
import { BUNDLE } from './bundle.resource-type';
|
||||||
import { DSpaceObject } from './dspace-object.model';
|
import { DSpaceObject } from './dspace-object.model';
|
||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { BITSTREAM } from './bitstream.resource-type';
|
||||||
|
import { Bitstream } from './bitstream.model';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
@@ -17,5 +24,19 @@ export class Bundle extends DSpaceObject {
|
|||||||
self: HALLink;
|
self: HALLink;
|
||||||
primaryBitstream: HALLink;
|
primaryBitstream: HALLink;
|
||||||
bitstreams: HALLink;
|
bitstreams: HALLink;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primary Bitstream of this Bundle
|
||||||
|
* Will be undefined unless the primaryBitstream {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(BITSTREAM)
|
||||||
|
primaryBitstream?: Observable<RemoteData<Bitstream>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of Bitstreams that are direct children of this Bundle
|
||||||
|
* Will be undefined unless the bitstreams {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(BITSTREAM, true)
|
||||||
|
bitstreams?: Observable<RemoteData<PaginatedList<Bitstream>>>;
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import { DSpaceObject } from './dspace-object.model';
|
|||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { License } from './license.model';
|
import { License } from './license.model';
|
||||||
import { LICENSE } from './license.resource-type';
|
import { LICENSE } from './license.resource-type';
|
||||||
import { ResourcePolicy } from './resource-policy.model';
|
import { ResourcePolicy } from '../resource-policy/models/resource-policy.model';
|
||||||
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
import { RESOURCE_POLICY } from '../resource-policy/models/resource-policy.resource-type';
|
||||||
import { COMMUNITY } from './community.resource-type';
|
import { COMMUNITY } from './community.resource-type';
|
||||||
import { Community } from './community.model';
|
import { Community } from './community.model';
|
||||||
import { ChildHALResource } from './child-hal-resource.model';
|
import { ChildHALResource } from './child-hal-resource.model';
|
||||||
|
@@ -67,6 +67,10 @@ export const getSucceededRemoteData = () =>
|
|||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
|
||||||
|
|
||||||
|
export const getSucceededRemoteWithNotEmptyData = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the first successful remotely retrieved object
|
* Get the first successful remotely retrieved object
|
||||||
*
|
*
|
||||||
@@ -84,6 +88,23 @@ export const getFirstSucceededRemoteDataPayload = () =>
|
|||||||
getRemoteDataPayload()
|
getRemoteDataPayload()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first successful remotely retrieved object with not empty payload
|
||||||
|
*
|
||||||
|
* You usually don't want to use this, it is a code smell.
|
||||||
|
* Work with the RemoteData object instead, that way you can
|
||||||
|
* handle loading and errors correctly.
|
||||||
|
*
|
||||||
|
* These operators were created as a first step in refactoring
|
||||||
|
* out all the instances where this is used incorrectly.
|
||||||
|
*/
|
||||||
|
export const getFirstSucceededRemoteDataWithNotEmptyPayload = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||||
|
source.pipe(
|
||||||
|
getSucceededRemoteWithNotEmptyData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the all successful remotely retrieved objects
|
* Get the all successful remotely retrieved objects
|
||||||
*
|
*
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
|
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
|
||||||
import { IDToUUIDSerializer } from '../cache/id-to-uuid-serializer';
|
|
||||||
import { ActionType } from '../cache/models/action-type.model';
|
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
|
||||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
|
||||||
import { HALLink } from './hal-link.model';
|
|
||||||
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
|
||||||
import { ResourceType } from './resource-type';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model class for a Resource Policy
|
|
||||||
*/
|
|
||||||
@typedObject
|
|
||||||
export class ResourcePolicy implements CacheableObject {
|
|
||||||
static type = RESOURCE_POLICY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The object type
|
|
||||||
*/
|
|
||||||
@excludeFromEquals
|
|
||||||
@autoserialize
|
|
||||||
type: ResourceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The action that is allowed by this Resource Policy
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
action: ActionType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name for this Resource Policy
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The uuid of the Group this Resource Policy applies to
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
groupUUID: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The universally unique identifier for this Resource Policy
|
|
||||||
* This UUID is generated client-side and isn't used by the backend.
|
|
||||||
* It is based on the ID, so it will be the same for each refresh.
|
|
||||||
*/
|
|
||||||
@deserializeAs(new IDToUUIDSerializer('resource-policy'), 'id')
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link HALLink}s for this ResourcePolicy
|
|
||||||
*/
|
|
||||||
@deserialize
|
|
||||||
_links: {
|
|
||||||
self: HALLink,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,6 +13,7 @@ import { getSucceededRemoteData } from '../../../core/shared/operators';
|
|||||||
import { ResourceType } from '../../../core/shared/resource-type';
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
import { hasValue, isNotEmpty, isNotUndefined } from '../../empty.util';
|
import { hasValue, isNotEmpty, isNotUndefined } from '../../empty.util';
|
||||||
import { NotificationsService } from '../../notifications/notifications.service';
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the create page for communities and collections
|
* Component representing the create page for communities and collections
|
||||||
@@ -76,7 +77,7 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
|||||||
const uploader = event.uploader;
|
const uploader = event.uploader;
|
||||||
|
|
||||||
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
||||||
this.dsoDataService.create(dso, uuid)
|
this.dsoDataService.create(dso, new RequestParam('parent', uuid))
|
||||||
.pipe(getSucceededRemoteData())
|
.pipe(getSucceededRemoteData())
|
||||||
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
||||||
if (isNotUndefined(dsoRD)) {
|
if (isNotUndefined(dsoRD)) {
|
||||||
|
@@ -3,6 +3,8 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { isObject } from 'lodash';
|
import { isObject } from 'lodash';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
import { isNull } from './empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the passed value is a NgbDateStruct.
|
* Returns true if the passed value is a NgbDateStruct.
|
||||||
*
|
*
|
||||||
@@ -56,3 +58,57 @@ export function dateToISOFormat(date: Date | NgbDateStruct): string {
|
|||||||
export function ngbDateStructToDate(date: NgbDateStruct): Date {
|
export function ngbDateStructToDate(date: NgbDateStruct): Date {
|
||||||
return new Date(date.year, (date.month - 1), date.day);
|
return new Date(date.year, (date.month - 1), date.day);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a NgbDateStruct object started from a string representing a date
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The Date to convert
|
||||||
|
* @return NgbDateStruct
|
||||||
|
* the NgbDateStruct object
|
||||||
|
*/
|
||||||
|
export function stringToNgbDateStruct(date: string): NgbDateStruct {
|
||||||
|
return dateToNgbDateStruct(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a NgbDateStruct object started from a Date object
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The Date to convert
|
||||||
|
* @return NgbDateStruct
|
||||||
|
* the NgbDateStruct object
|
||||||
|
*/
|
||||||
|
export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
|
||||||
|
if (isNull(date)) {
|
||||||
|
date = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: date.getFullYear(),
|
||||||
|
month: date.getMonth() + 1,
|
||||||
|
day: date.getDate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date in simplified format (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The date to format
|
||||||
|
* @return string
|
||||||
|
* the formatted date
|
||||||
|
*/
|
||||||
|
export function dateToString(date: Date | NgbDateStruct): string {
|
||||||
|
const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date);
|
||||||
|
|
||||||
|
let year = dateObj.getFullYear().toString();
|
||||||
|
let month = (dateObj.getMonth() + 1).toString();
|
||||||
|
let day = dateObj.getDate().toString();
|
||||||
|
|
||||||
|
year = (year.length === 1) ? '0' + year : year;
|
||||||
|
month = (month.length === 1) ? '0' + month : month;
|
||||||
|
day = (day.length === 1) ? '0' + day : day;
|
||||||
|
const dateStr = `${year}-${month}-${day}`;
|
||||||
|
return moment.utc(dateStr, 'YYYYMMDD').format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<form (ngSubmit)="submit()"
|
<form class="form-login"
|
||||||
|
(ngSubmit)="submit()"
|
||||||
[formGroup]="form" novalidate>
|
[formGroup]="form" novalidate>
|
||||||
<label for="inputEmail" class="sr-only">{{"login.form.email" | translate}}</label>
|
<label for="inputEmail" class="sr-only">{{"login.form.email" | translate}}</label>
|
||||||
<input id="inputEmail"
|
<input id="inputEmail"
|
||||||
|
10
src/app/shared/mocks/mock-resource-policy-service.ts
Normal file
10
src/app/shared/mocks/mock-resource-policy-service.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
|
||||||
|
export function getMockResourcePolicyService(): ResourcePolicyService {
|
||||||
|
return jasmine.createSpyObj('resourcePolicyService', {
|
||||||
|
searchByResource: jasmine.createSpy('searchByResource'),
|
||||||
|
create: jasmine.createSpy('create'),
|
||||||
|
delete: jasmine.createSpy('delete'),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
});
|
||||||
|
}
|
16
src/app/shared/ng-for-track-by-id.directive.ts
Normal file
16
src/app/shared/ng-for-track-by-id.directive.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Directive, Host } from '@angular/core';
|
||||||
|
import { NgForOf } from '@angular/common';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
// tslint:disable-next-line:directive-selector
|
||||||
|
selector: '[ngForTrackById]',
|
||||||
|
})
|
||||||
|
export class NgForTrackByIdDirective<T extends DSpaceObject> {
|
||||||
|
|
||||||
|
constructor(@Host() private ngFor: NgForOf<T>) {
|
||||||
|
this.ngFor.ngForTrackBy = (index: number, dso: T) => (dso) ? dso.id : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h4 class="mb-3">{{'resource-policies.create.page.heading' | translate}} {{targetResourceName}}</h4>
|
||||||
|
|
||||||
|
<ds-resource-policy-form [isProcessing]="isProcessing()"
|
||||||
|
(reset)="redirectToAuthorizationsPage()"
|
||||||
|
(submit)="createResourcePolicy($event)"></ds-resource-policy-form>
|
||||||
|
</div>
|
@@ -0,0 +1,265 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
|
} from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { ResourcePolicyCreateComponent } from './resource-policy-create.component';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../../mocks/mock-resource-policy-service';
|
||||||
|
import { getMockLinkService } from '../../mocks/link-service.mock';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../../../+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { submittedResourcePolicy } from '../form/resource-policy-form.component.spec';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { EPersonMock } from '../../testing/eperson.mock';
|
||||||
|
|
||||||
|
describe('ResourcePolicyCreateComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyCreateComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyCreateComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let eventPayload: ResourcePolicyEvent;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'itemUUID',
|
||||||
|
id: 'itemUUID',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [{
|
||||||
|
value: 'test item'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([])
|
||||||
|
});
|
||||||
|
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
resourcePolicyTarget: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-create></ds-resource-policy-create>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyCreateComponent', inject([ResourcePolicyCreateComponent], (app: ResourcePolicyCreateComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyCreateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.targetResourceUUID).toBe('itemUUID');
|
||||||
|
expect(compAsAny.targetResourceName).toBe('test item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to authorizations page', () => {
|
||||||
|
comp.redirectToAuthorizationsPage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when is Processing', () => {
|
||||||
|
compAsAny.processing$.next(true);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when is not Processing', () => {
|
||||||
|
compAsAny.processing$.next(false);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when target type is group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when creation is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', null, eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when creation is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', null, eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when target type of created policy is eperson', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'eperson',
|
||||||
|
uuid: EPersonMock.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when creation is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when creation is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,112 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { first, map, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-create',
|
||||||
|
templateUrl: './resource-policy-create.component.html'
|
||||||
|
})
|
||||||
|
export class ResourcePolicyCreateComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the resource target of the policy
|
||||||
|
*/
|
||||||
|
public targetResourceName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission creation operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of the resource target of the policy
|
||||||
|
*/
|
||||||
|
private targetResourceUUID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.data.pipe(
|
||||||
|
map((data) => data),
|
||||||
|
take(1)
|
||||||
|
).subscribe((data: any) => {
|
||||||
|
this.targetResourceUUID = (data.resourcePolicyTarget as RemoteData<DSpaceObject>).payload.id;
|
||||||
|
this.targetResourceName = this.dsoNameService.getName((data.resourcePolicyTarget as RemoteData<DSpaceObject>).payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if an operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessing(): Observable<boolean> {
|
||||||
|
return this.processing$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the authorizations page
|
||||||
|
*/
|
||||||
|
redirectToAuthorizationsPage(): void {
|
||||||
|
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new resource policy
|
||||||
|
*
|
||||||
|
* @param event The {{ResourcePolicyEvent}} emitted
|
||||||
|
*/
|
||||||
|
createResourcePolicy(event: ResourcePolicyEvent): void {
|
||||||
|
this.processing$.next(true);
|
||||||
|
let response$;
|
||||||
|
if (event.target.type === 'eperson') {
|
||||||
|
response$ = this.resourcePolicyService.create(event.object, this.targetResourceUUID, event.target.uuid);
|
||||||
|
} else {
|
||||||
|
response$ = this.resourcePolicyService.create(event.object, this.targetResourceUUID, null, event.target.uuid);
|
||||||
|
}
|
||||||
|
response$.pipe(
|
||||||
|
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
|
||||||
|
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
|
||||||
|
this.processing$.next(false);
|
||||||
|
if (responseRD.hasSucceeded) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.create.page.success.content'));
|
||||||
|
this.redirectToAuthorizationsPage();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.create.page.failure.content'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h4 class="mb-3">{{'resource-policies.edit.page.heading' | translate}} {{resourcePolicy.id}}</h4>
|
||||||
|
|
||||||
|
<ds-resource-policy-form [resourcePolicy]="resourcePolicy"
|
||||||
|
[isProcessing]="isProcessing()"
|
||||||
|
(reset)="redirectToAuthorizationsPage()"
|
||||||
|
(submit)="updateResourcePolicy($event)"></ds-resource-policy-form>
|
||||||
|
</div>
|
@@ -0,0 +1,220 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
|
} from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../../mocks/mock-resource-policy-service';
|
||||||
|
import { getMockLinkService } from '../../mocks/link-service.mock';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { submittedResourcePolicy } from '../form/resource-policy-form.component.spec';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { ResourcePolicyEditComponent } from './resource-policy-edit.component';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
|
||||||
|
describe('ResourcePolicyEditComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyEditComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyEditComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let eventPayload: ResourcePolicyEvent;
|
||||||
|
let updatedObject;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
resourcePolicy: createSuccessfulRemoteDataObject(resourcePolicy)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-edit></ds-resource-policy-edit>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyEditComponent', inject([ResourcePolicyEditComponent], (app: ResourcePolicyEditComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyEditComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.resourcePolicy).toEqual(resourcePolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to authorizations page', () => {
|
||||||
|
comp.redirectToAuthorizationsPage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when is Processing', () => {
|
||||||
|
compAsAny.processing$.next(true);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when is not Processing', () => {
|
||||||
|
compAsAny.processing$.next(false);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
|
||||||
|
compAsAny.resourcePolicy = resourcePolicy;
|
||||||
|
|
||||||
|
updatedObject = Object.assign({}, submittedResourcePolicy, {
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
type: RESOURCE_POLICY.value,
|
||||||
|
_links: resourcePolicy._links
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when update is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.updateResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.update).toHaveBeenCalledWith(updatedObject);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when update is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.updateResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.update).toHaveBeenCalledWith(updatedObject);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,102 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { first, map, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-edit',
|
||||||
|
templateUrl: './resource-policy-edit.component.html'
|
||||||
|
})
|
||||||
|
export class ResourcePolicyEditComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource policy object to edit
|
||||||
|
*/
|
||||||
|
public resourcePolicy: ResourcePolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission editing operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.data.pipe(
|
||||||
|
map((data) => data),
|
||||||
|
take(1)
|
||||||
|
).subscribe((data: any) => {
|
||||||
|
this.resourcePolicy = (data.resourcePolicy as RemoteData<ResourcePolicy>).payload;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if an operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessing(): Observable<boolean> {
|
||||||
|
return this.processing$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the authorizations page
|
||||||
|
*/
|
||||||
|
redirectToAuthorizationsPage() {
|
||||||
|
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a resource policy
|
||||||
|
*
|
||||||
|
* @param event The {{ResourcePolicyEvent}} emitted
|
||||||
|
*/
|
||||||
|
updateResourcePolicy(event: ResourcePolicyEvent) {
|
||||||
|
this.processing$.next(true);
|
||||||
|
const updatedObject = Object.assign({}, event.object, {
|
||||||
|
id: this.resourcePolicy.id,
|
||||||
|
type: RESOURCE_POLICY.value,
|
||||||
|
_links: this.resourcePolicy._links
|
||||||
|
});
|
||||||
|
this.resourcePolicyService.update(updatedObject).pipe(
|
||||||
|
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
|
||||||
|
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
|
||||||
|
this.processing$.next(false);
|
||||||
|
if (responseRD.hasSucceeded) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.edit.page.success.content'));
|
||||||
|
this.redirectToAuthorizationsPage();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.edit.page.failure.content'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
<div class="mt-3" @fadeInOut>
|
||||||
|
<ds-eperson-search-box *ngIf="isListOfEPerson" (search)="onSearch($event)"></ds-eperson-search-box>
|
||||||
|
<ds-group-search-box *ngIf="!isListOfEPerson" (search)="onSearch($event)"></ds-group-search-box>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(getList() | async)?.payload?.totalElements > 0"
|
||||||
|
[paginationOptions]="paginationOptions"
|
||||||
|
[collectionSize]="(getList() | async)?.payload?.totalElements"
|
||||||
|
[disableRouteParameterUpdate]="true"
|
||||||
|
[hideGear]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="groups" class="table table-sm table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.id' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.name' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.action' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let entry of (getList() | async)?.payload?.page"
|
||||||
|
[class.table-primary]="isSelected(entry) | async">
|
||||||
|
<td>{{entry.id}}</td>
|
||||||
|
<td>{{dsoNameService.getName(entry)}}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" (click)="emitSelect(entry)">
|
||||||
|
{{'resource-policies.form.eperson-group-list.select.btn' | translate}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
</div>
|
@@ -0,0 +1,287 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../../../mocks/request.service.mock';
|
||||||
|
import { EpersonGroupListComponent, SearchEvent } from './eperson-group-list.component';
|
||||||
|
import { EPersonMock } from '../../../testing/eperson.mock';
|
||||||
|
import { GroupMock } from '../../../testing/group-mock';
|
||||||
|
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('EpersonGroupListComponent test suite', () => {
|
||||||
|
let comp: EpersonGroupListComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<EpersonGroupListComponent>;
|
||||||
|
let de;
|
||||||
|
let groupService: any;
|
||||||
|
let epersonService: any;
|
||||||
|
|
||||||
|
const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions()
|
||||||
|
paginationOptions.id = uniqueId('eperson-group-list-pagination-test');
|
||||||
|
paginationOptions.pageSize = 5;
|
||||||
|
|
||||||
|
const mockEpersonService = jasmine.createSpyObj('epersonService',
|
||||||
|
{
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
searchByScope: jasmine.createSpy('searchByScope'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linkPath: 'epersons'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockGroupService = jasmine.createSpyObj('groupService',
|
||||||
|
{
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
searchGroups: jasmine.createSpy('searchGroups'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linkPath: 'groups'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const epersonPaginatedList = new PaginatedList(new PageInfo(), [EPersonMock, EPersonMock]);
|
||||||
|
const epersonPaginatedListRD = createSuccessfulRemoteDataObject(epersonPaginatedList);
|
||||||
|
|
||||||
|
const groupPaginatedList = new PaginatedList(new PageInfo(), [GroupMock, GroupMock]);
|
||||||
|
const groupPaginatedListRD = createSuccessfulRemoteDataObject(groupPaginatedList);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
NoopAnimationsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EPersonDataService, useValue: mockEpersonService },
|
||||||
|
{ provide: GroupDataService, useValue: mockGroupService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-eperson-group-list [isListOfEPerson]="isListOfEPerson" [initSelected]="initSelected"></ds-eperson-group-list>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create EpersonGroupListComponent', inject([EpersonGroupListComponent], (app: EpersonGroupListComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is list of eperson', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonGroupListComponent);
|
||||||
|
epersonService = TestBed.get(EPersonDataService);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isListOfEPerson = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject EPersonDataService', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.dataService).toBeDefined();
|
||||||
|
expect(comp.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init entrySelectedId', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.initSelected = EPersonMock.id;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init the list of eperson', () => {
|
||||||
|
epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.list$.value).toEqual(epersonPaginatedListRD);
|
||||||
|
expect(comp.getList()).toBeObservable(cold('a', {
|
||||||
|
a: epersonPaginatedListRD
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit select event', () => {
|
||||||
|
spyOn(comp.select, 'emit');
|
||||||
|
comp.emitSelect(EPersonMock);
|
||||||
|
|
||||||
|
expect(comp.select.emit).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when entry is selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when entry is not selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next('');
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on page change', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onPageChange(2);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is list of group', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonGroupListComponent);
|
||||||
|
groupService = TestBed.get(GroupDataService);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isListOfEPerson = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject GroupDataService', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.dataService).toBeDefined();
|
||||||
|
expect(comp.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init entrySelectedId', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.initSelected = GroupMock.id;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init the list of group', () => {
|
||||||
|
groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.list$.value).toEqual(groupPaginatedListRD);
|
||||||
|
expect(comp.getList()).toBeObservable(cold('a', {
|
||||||
|
a: groupPaginatedListRD
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit select event', () => {
|
||||||
|
spyOn(comp.select, 'emit');
|
||||||
|
comp.emitSelect(GroupMock);
|
||||||
|
|
||||||
|
expect(comp.select.emit).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when entry is selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when entry is not selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next('');
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on page change', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onPageChange(2);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on search triggered', () => {
|
||||||
|
const options: PaginationComponentOptions = comp.paginationOptions
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onSearch(event);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalledWith(options, 'metadata', 'test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
isListOfEPerson = true;
|
||||||
|
initSelected = '';
|
||||||
|
}
|
@@ -0,0 +1,201 @@
|
|||||||
|
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
import { map, take } from 'rxjs/operators';
|
||||||
|
import { uniqueId } from 'lodash'
|
||||||
|
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
|
||||||
|
import { DataService } from '../../../../core/data/data.service';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../empty.util';
|
||||||
|
import { FindListOptions } from '../../../../core/data/request.models';
|
||||||
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { getDataServiceFor } from '../../../../core/cache/builders/build-decorators';
|
||||||
|
import { EPERSON } from '../../../../core/eperson/models/eperson.resource-type';
|
||||||
|
import { GROUP } from '../../../../core/eperson/models/group.resource-type';
|
||||||
|
import { ResourceType } from '../../../../core/shared/resource-type';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { fadeInOut } from '../../../animations/fade';
|
||||||
|
|
||||||
|
export interface SearchEvent {
|
||||||
|
scope: string;
|
||||||
|
query: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-eperson-group-list',
|
||||||
|
styleUrls: ['./eperson-group-list.component.scss'],
|
||||||
|
templateUrl: './eperson-group-list.component.html',
|
||||||
|
animations: [
|
||||||
|
fadeInOut
|
||||||
|
]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that shows a list of eperson or group
|
||||||
|
*/
|
||||||
|
export class EpersonGroupListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing id component should list eperson or group
|
||||||
|
*/
|
||||||
|
@Input() isListOfEPerson = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of eperson or group initially selected
|
||||||
|
*/
|
||||||
|
@Input() initSelected: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a eperson or group is selected.
|
||||||
|
* Event's payload equals to DSpaceObject.
|
||||||
|
*/
|
||||||
|
@Output() select: EventEmitter<DSpaceObject> = new EventEmitter<DSpaceObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current search query
|
||||||
|
*/
|
||||||
|
public currentSearchQuery = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current search scope
|
||||||
|
*/
|
||||||
|
public currentSearchScope = 'metadata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list
|
||||||
|
*/
|
||||||
|
public paginationOptions: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data service used to make request.
|
||||||
|
* It could be EPersonDataService or GroupDataService
|
||||||
|
*/
|
||||||
|
private dataService: DataService<DSpaceObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of eperson or group
|
||||||
|
*/
|
||||||
|
private list$: BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>> = new BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>>({} as any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson or group's id selected
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
private entrySelectedId: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables and inject the properly DataService
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {Injector} parentInjector
|
||||||
|
*/
|
||||||
|
constructor(public dsoNameService: DSONameService, private parentInjector: Injector) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
const resourceType: ResourceType = (this.isListOfEPerson) ? EPERSON : GROUP;
|
||||||
|
const provider = getDataServiceFor(resourceType);
|
||||||
|
this.dataService = Injector.create({
|
||||||
|
providers: [],
|
||||||
|
parent: this.parentInjector
|
||||||
|
}).get(provider);
|
||||||
|
this.paginationOptions.id = uniqueId('eperson-group-list-pagination');
|
||||||
|
this.paginationOptions.pageSize = 5;
|
||||||
|
|
||||||
|
if (this.initSelected) {
|
||||||
|
this.entrySelectedId.next(this.initSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called when an entry is selected.
|
||||||
|
* Emit a new select Event
|
||||||
|
*
|
||||||
|
* @param entry The eperson or group selected
|
||||||
|
*/
|
||||||
|
emitSelect(entry: DSpaceObject): void {
|
||||||
|
this.select.emit(entry);
|
||||||
|
this.entrySelectedId.next(entry.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of eperson or group
|
||||||
|
*/
|
||||||
|
getList(): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||||
|
return this.list$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if a table row is selected
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isSelected(entry: DSpaceObject): Observable<boolean> {
|
||||||
|
return this.entrySelectedId.asObservable().pipe(
|
||||||
|
map((selectedId) => isNotEmpty(selectedId) && selectedId === entry.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on page change
|
||||||
|
*/
|
||||||
|
onPageChange(page: number): void {
|
||||||
|
this.paginationOptions.currentPage = page;
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on search
|
||||||
|
*/
|
||||||
|
onSearch(searchEvent: SearchEvent) {
|
||||||
|
this.currentSearchQuery = searchEvent.query;
|
||||||
|
this.currentSearchScope = searchEvent.scope;
|
||||||
|
this.paginationOptions.currentPage = 1;
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a paginate list of eperson or group
|
||||||
|
*/
|
||||||
|
updateList(config: PaginationComponentOptions, scope: string, query: string): void {
|
||||||
|
const options: FindListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
|
elementsPerPage: config.pageSize,
|
||||||
|
currentPage: config.currentPage
|
||||||
|
});
|
||||||
|
|
||||||
|
const search$: Observable<RemoteData<PaginatedList<DSpaceObject>>> = this.isListOfEPerson ?
|
||||||
|
(this.dataService as EPersonDataService).searchByScope(scope, query, options) :
|
||||||
|
(this.dataService as GroupDataService).searchGroups(query, options);
|
||||||
|
|
||||||
|
this.subs.push(search$.pipe(take(1))
|
||||||
|
.subscribe((list: RemoteData<PaginatedList<DSpaceObject>>) => {
|
||||||
|
this.list$.next(list)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.list$ = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
<form class="d-flex justify-content-between"
|
||||||
|
[formGroup]="searchForm"
|
||||||
|
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
|
||||||
|
<div>
|
||||||
|
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||||
|
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
||||||
|
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 mr-3 ml-3">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit" class="search-button btn btn-secondary">
|
||||||
|
{{ labelPrefix + 'search.button' | translate }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
|
||||||
|
{{ labelPrefix + 'button.see-all' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@@ -0,0 +1,115 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createTestComponent } from '../../../../testing/utils.test';
|
||||||
|
import { EpersonSearchBoxComponent } from './eperson-search-box.component';
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
describe('EpersonSearchBoxComponent test suite', () => {
|
||||||
|
let comp: EpersonSearchBoxComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<EpersonSearchBoxComponent>;
|
||||||
|
let de;
|
||||||
|
let formBuilder: FormBuilder;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EpersonSearchBoxComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
EpersonSearchBoxComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-group-search-box></ds-group-search-box>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create EpersonSearchBoxComponent', inject([EpersonSearchBoxComponent], (app: EpersonSearchBoxComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonSearchBoxComponent);
|
||||||
|
formBuilder = TestBed.get(FormBuilder);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the form', () => {
|
||||||
|
comp.searchForm = formBuilder.group(({
|
||||||
|
query: 'test',
|
||||||
|
}));
|
||||||
|
|
||||||
|
comp.reset();
|
||||||
|
|
||||||
|
expect(comp.searchForm.controls.query.value).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit new search event', () => {
|
||||||
|
const data = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp.search, 'emit');
|
||||||
|
|
||||||
|
comp.submit(data);
|
||||||
|
|
||||||
|
expect(comp.search.emit).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
import { isNotNull } from '../../../../empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component used to show a search box for epersons.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-eperson-search-box',
|
||||||
|
templateUrl: './eperson-search-box.component.html',
|
||||||
|
})
|
||||||
|
export class EpersonSearchBoxComponent {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.epeople.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search form
|
||||||
|
*/
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a search is triggred.
|
||||||
|
* Event's payload is a SearchEvent.
|
||||||
|
*/
|
||||||
|
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the search form
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a new search event
|
||||||
|
* @param data Form data
|
||||||
|
*/
|
||||||
|
submit(data: any) {
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: isNotNull(data) ? data.scope : 'metadata',
|
||||||
|
query: isNotNull(data) ? data.query : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
this.search.emit(event)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
<form class="d-flex justify-content-between"
|
||||||
|
[formGroup]="searchForm"
|
||||||
|
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
|
||||||
|
<div class="flex-grow-1 mr-3">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit" class="search-button btn btn-secondary">
|
||||||
|
{{ labelPrefix + 'search.button' | translate }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
|
||||||
|
{{ labelPrefix + 'button.see-all' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@@ -0,0 +1,114 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createTestComponent } from '../../../../testing/utils.test';
|
||||||
|
import { GroupSearchBoxComponent } from './group-search-box.component';
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
describe('GroupSearchBoxComponent test suite', () => {
|
||||||
|
let comp: GroupSearchBoxComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<GroupSearchBoxComponent>;
|
||||||
|
let de;
|
||||||
|
let formBuilder: FormBuilder;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
GroupSearchBoxComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
GroupSearchBoxComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-group-search-box></ds-group-search-box>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create GroupSearchBoxComponent', inject([GroupSearchBoxComponent], (app: GroupSearchBoxComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(GroupSearchBoxComponent);
|
||||||
|
formBuilder = TestBed.get(FormBuilder);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the form', () => {
|
||||||
|
comp.searchForm = formBuilder.group(({
|
||||||
|
query: 'test',
|
||||||
|
}));
|
||||||
|
|
||||||
|
comp.reset();
|
||||||
|
|
||||||
|
expect(comp.searchForm.controls.query.value).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit new search event', () => {
|
||||||
|
const data = {
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: '',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp.search, 'emit');
|
||||||
|
|
||||||
|
comp.submit(data);
|
||||||
|
|
||||||
|
expect(comp.search.emit).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component used to show a search box for groups.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-group-search-box',
|
||||||
|
templateUrl: './group-search-box.component.html',
|
||||||
|
})
|
||||||
|
export class GroupSearchBoxComponent {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.groups.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search form
|
||||||
|
*/
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a search is triggred.
|
||||||
|
* Event's payload is a SearchEvent.
|
||||||
|
*/
|
||||||
|
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the search form
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a new search event
|
||||||
|
* @param data Form data
|
||||||
|
*/
|
||||||
|
submit(data: any) {
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: '',
|
||||||
|
query: data.query
|
||||||
|
}
|
||||||
|
this.search.emit(event)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
<div>
|
||||||
|
<ds-form *ngIf="formModel"
|
||||||
|
#formRef="formComponent"
|
||||||
|
[formId]="formId"
|
||||||
|
[formModel]="formModel"
|
||||||
|
[displaySubmit]="false"></ds-form>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<label for="ResourcePolicyObject">{{'resource-policies.form.eperson-group-list.label' | translate}}</label>
|
||||||
|
<input id="ResourcePolicyObject" class="form-control mb-3" type="text" readonly [value]="getResourcePolicyTargetName()">
|
||||||
|
<ngb-tabset *ngIf="canSetGrant()" type="pills">
|
||||||
|
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.eperson' | translate">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-eperson-group-list (select)="updateObjectSelected($event, true)"></ds-eperson-group-list>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.group' | translate">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-eperson-group-list [isListOfEPerson]="false"
|
||||||
|
(select)="updateObjectSelected($event, false)"></ds-eperson-group-list>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
</ngb-tabset>
|
||||||
|
<div>
|
||||||
|
<hr>
|
||||||
|
<div class="form-group row">
|
||||||
|
|
||||||
|
<div class="col text-right">
|
||||||
|
<button type="reset"
|
||||||
|
class="btn btn-default"
|
||||||
|
[disabled]="(isProcessing | async)"
|
||||||
|
(click)="onReset()">{{'form.cancel' | translate}}</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
[disabled]="!(isFormValid() | async) || (isProcessing | async)"
|
||||||
|
(click)="onSubmit()">
|
||||||
|
<span *ngIf="(isProcessing | async)">
|
||||||
|
<i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!(isProcessing | async)">
|
||||||
|
{{'form.submit' | translate}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,427 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../../mocks/request.service.mock';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { ResourcePolicyEvent, ResourcePolicyFormComponent } from './resource-policy-form.component';
|
||||||
|
import { FormService } from '../../form/form.service';
|
||||||
|
import { getMockFormService } from '../../mocks/form-service.mock';
|
||||||
|
import { FormBuilderService } from '../../form/builder/form-builder.service';
|
||||||
|
import { EpersonGroupListComponent } from './eperson-group-list/eperson-group-list.component';
|
||||||
|
import { FormComponent } from '../../form/form.component';
|
||||||
|
import { stringToNgbDateStruct } from '../../date.util';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
import { EPersonMock } from '../../testing/eperson.mock';
|
||||||
|
|
||||||
|
export const mockResourcePolicyFormData = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
value: 'name',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'name',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
description: [
|
||||||
|
{
|
||||||
|
value: 'description',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'description',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
policyType: [
|
||||||
|
{
|
||||||
|
value: 'TYPE_WORKFLOW',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'TYPE_WORKFLOW',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
action: [
|
||||||
|
{
|
||||||
|
value: 'WRITE',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'WRITE',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: {
|
||||||
|
start: [
|
||||||
|
{
|
||||||
|
value: { year: '2019', month: '04', day: '14' },
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: '2019-04-14',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
end: [
|
||||||
|
{
|
||||||
|
value: { year: '2020', month: '04', day: '14' },
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: '2020-04-14',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const submittedResourcePolicy = Object.assign(new ResourcePolicy(), {
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
policyType: PolicyType.TYPE_WORKFLOW,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: '2019-04-14T00:00:00Z',
|
||||||
|
endDate: '2020-04-14T00:00:00Z',
|
||||||
|
type: RESOURCE_POLICY
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ResourcePolicyFormComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyFormComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyFormComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: '2019-04-14',
|
||||||
|
endDate: '2020-04-14',
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const epersonService = jasmine.createSpyObj('epersonService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll')
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupService = jasmine.createSpyObj('groupService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll')
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
FormComponent,
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
ResourcePolicyFormComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EPersonDataService, useValue: epersonService },
|
||||||
|
{ provide: FormService, useValue: getMockFormService() },
|
||||||
|
{ provide: GroupDataService, useValue: groupService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
FormBuilderService,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ResourcePolicyFormComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-form [resourcePolicy]="resourcePolicy" [isProcessing]="isProcessing"></ds-resource-policy-form>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyFormComponent', inject([ResourcePolicyFormComponent], (app: ResourcePolicyFormComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when resource policy is not provided', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init form model properly', () => {
|
||||||
|
spyOn(compAsAny, 'isFormValid').and.returnValue(observableOf(false));
|
||||||
|
spyOn(compAsAny, 'initModelsValue').and.callThrough();
|
||||||
|
spyOn(compAsAny, 'buildResourcePolicyForm').and.callThrough();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.buildResourcePolicyForm).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.initModelsValue).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.formModel.length).toBe(5);
|
||||||
|
expect(compAsAny.subs.length).toBe(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should can set grant', () => {
|
||||||
|
expect(comp.canSetGrant()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have a target name', () => {
|
||||||
|
expect(comp.getResourcePolicyTargetName()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit reset event', () => {
|
||||||
|
spyOn(compAsAny.reset, 'emit');
|
||||||
|
comp.onReset();
|
||||||
|
expect(compAsAny.reset.emit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update resource policy grant object properly', () => {
|
||||||
|
comp.updateObjectSelected(EPersonMock, true);
|
||||||
|
|
||||||
|
expect(comp.resourcePolicyGrant).toEqual(EPersonMock);
|
||||||
|
expect(comp.resourcePolicyGrantType).toBe('eperson');
|
||||||
|
|
||||||
|
comp.updateObjectSelected(GroupMock, false);
|
||||||
|
|
||||||
|
expect(comp.resourcePolicyGrant).toEqual(GroupMock);
|
||||||
|
expect(comp.resourcePolicyGrantType).toBe('group');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when resource policy is provided', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init form model properly', () => {
|
||||||
|
spyOn(compAsAny, 'isFormValid').and.returnValue(observableOf(false));
|
||||||
|
spyOn(compAsAny, 'initModelsValue').and.callThrough();
|
||||||
|
spyOn(compAsAny, 'buildResourcePolicyForm').and.callThrough();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.buildResourcePolicyForm).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.initModelsValue).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.formModel.length).toBe(5);
|
||||||
|
expect(compAsAny.subs.length).toBe(1);
|
||||||
|
expect(compAsAny.formModel[2].value).toBe('TYPE_SUBMISSION');
|
||||||
|
expect(compAsAny.formModel[3].value).toBe('READ');
|
||||||
|
expect(compAsAny.formModel[4].get(0).value).toEqual(stringToNgbDateStruct('2019-04-14'));
|
||||||
|
expect(compAsAny.formModel[4].get(1).value).toEqual(stringToNgbDateStruct('2020-04-14'));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init resourcePolicyGrant properly', () => {
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.ngOnInit());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyGrant).toEqual(GroupMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not can set grant', () => {
|
||||||
|
expect(comp.canSetGrant()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a target name', () => {
|
||||||
|
compAsAny.resourcePolicyGrant = GroupMock;
|
||||||
|
|
||||||
|
expect(comp.getResourcePolicyTargetName()).toBe('testgroupname');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when form is valid', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
compAsAny.formService.isValid.and.returnValue(observableOf(true));
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
comp.resourcePolicyGrant = GroupMock;
|
||||||
|
comp.resourcePolicyGrantType = 'group';
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have submit button disabled when submission is valid', () => {
|
||||||
|
|
||||||
|
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
|
||||||
|
|
||||||
|
expect(depositBtn.nativeElement.disabled).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit submit event', () => {
|
||||||
|
spyOn(compAsAny.submit, 'emit');
|
||||||
|
spyOn(compAsAny, 'createResourcePolicyByFormData').and.callThrough();
|
||||||
|
compAsAny.formService.getFormData.and.returnValue(observableOf(mockResourcePolicyFormData));
|
||||||
|
const eventPayload: ResourcePolicyEvent = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.onSubmit());
|
||||||
|
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.submit.emit).toHaveBeenCalledWith(eventPayload);
|
||||||
|
expect(compAsAny.createResourcePolicyByFormData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when form is not valid', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
compAsAny.formService.isValid.and.returnValue(observableOf(false));
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have submit button disabled when submission is valid', () => {
|
||||||
|
|
||||||
|
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
|
||||||
|
|
||||||
|
expect(depositBtn.nativeElement.disabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
resourcePolicy = null;
|
||||||
|
isProcessing = observableOf(false);
|
||||||
|
}
|
@@ -0,0 +1,317 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, of as observableOf, race as observableRace } from 'rxjs';
|
||||||
|
import { filter, map, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
DynamicDatePickerModel,
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormGroupModel,
|
||||||
|
DynamicSelectModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { DsDynamicInputModel } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
|
import {
|
||||||
|
RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT,
|
||||||
|
RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_LAYOUT,
|
||||||
|
RESOURCE_POLICY_FORM_NAME_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_LAYOUT
|
||||||
|
} from './resource-policy-form.model';
|
||||||
|
import { DsDynamicTextAreaModel } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../../empty.util';
|
||||||
|
import { FormService } from '../../form/form.service';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { dateToISOFormat, stringToNgbDateStruct } from '../../date.util';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
|
||||||
|
export interface ResourcePolicyEvent {
|
||||||
|
object: ResourcePolicy,
|
||||||
|
target: {
|
||||||
|
type: string,
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-form',
|
||||||
|
templateUrl: './resource-policy-form.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that show form for adding/editing a resource policy
|
||||||
|
*/
|
||||||
|
export class ResourcePolicyFormComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If given contains the resource policy to edit
|
||||||
|
* @type {ResourcePolicy}
|
||||||
|
*/
|
||||||
|
@Input() resourcePolicy: ResourcePolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if form submit operation is processing
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
@Input() isProcessing: Observable<boolean> = observableOf(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when form is canceled.
|
||||||
|
* Event's payload is empty.
|
||||||
|
*/
|
||||||
|
@Output() reset: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when form is submitted.
|
||||||
|
* Event's payload equals to a new ResourcePolicy.
|
||||||
|
*/
|
||||||
|
@Output() submit: EventEmitter<ResourcePolicyEvent> = new EventEmitter<ResourcePolicyEvent>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public formId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form model
|
||||||
|
* @type {DynamicFormControlModel[]}
|
||||||
|
*/
|
||||||
|
public formModel: DynamicFormControlModel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson or group that will be grant of the permission
|
||||||
|
* @type {DSpaceObject}
|
||||||
|
*/
|
||||||
|
public resourcePolicyGrant: DSpaceObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the object that will be grant of the permission. It could be 'eperson' or 'group'
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public resourcePolicyGrantType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if component is active
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private isActive: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {EPersonDataService} ePersonService
|
||||||
|
* @param {FormService} formService
|
||||||
|
* @param {GroupDataService} groupService
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private ePersonService: EPersonDataService,
|
||||||
|
private formService: FormService,
|
||||||
|
private groupService: GroupDataService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the form model
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isActive = true;
|
||||||
|
this.formId = this.formService.getUniqueId('resource-policy-form');
|
||||||
|
this.formModel = this.buildResourcePolicyForm();
|
||||||
|
|
||||||
|
if (!this.canSetGrant()) {
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourcePolicy._links.eperson.href);
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourcePolicy._links.group.href);
|
||||||
|
const epersonRD$ = this.ePersonService.findByHref(this.resourcePolicy._links.eperson.href).pipe(
|
||||||
|
getSucceededRemoteData()
|
||||||
|
);
|
||||||
|
const groupRD$ = this.groupService.findByHref(this.resourcePolicy._links.group.href).pipe(
|
||||||
|
getSucceededRemoteData()
|
||||||
|
);
|
||||||
|
const dsoRD$: Observable<RemoteData<DSpaceObject>> = observableRace(epersonRD$, groupRD$);
|
||||||
|
this.subs.push(
|
||||||
|
dsoRD$.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
).subscribe((dsoRD: RemoteData<DSpaceObject>) => {
|
||||||
|
this.resourcePolicyGrant = dsoRD.payload;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to check if the form status is valid or not
|
||||||
|
*
|
||||||
|
* @return Observable that emits the form status
|
||||||
|
*/
|
||||||
|
isFormValid(): Observable<boolean> {
|
||||||
|
return this.formService.isValid(this.formId).pipe(
|
||||||
|
map((isValid: boolean) => isValid && isNotEmpty(this.resourcePolicyGrant))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the form model
|
||||||
|
*
|
||||||
|
* @return the form models
|
||||||
|
*/
|
||||||
|
private buildResourcePolicyForm(): DynamicFormControlModel[] {
|
||||||
|
const formModel: DynamicFormControlModel[] = [];
|
||||||
|
// TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented
|
||||||
|
const policyTypeConf = Object.assign({}, RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG, {
|
||||||
|
disabled: isNotEmpty(this.resourcePolicy)
|
||||||
|
});
|
||||||
|
// TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented
|
||||||
|
const actionConf = Object.assign({}, RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG, {
|
||||||
|
disabled: isNotEmpty(this.resourcePolicy)
|
||||||
|
});
|
||||||
|
formModel.push(
|
||||||
|
new DsDynamicInputModel(RESOURCE_POLICY_FORM_NAME_CONFIG),
|
||||||
|
new DsDynamicTextAreaModel(RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG),
|
||||||
|
new DynamicSelectModel(policyTypeConf),
|
||||||
|
new DynamicSelectModel(actionConf)
|
||||||
|
);
|
||||||
|
|
||||||
|
const startDateModel = new DynamicDatePickerModel(
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_LAYOUT
|
||||||
|
);
|
||||||
|
const endDateModel = new DynamicDatePickerModel(
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_LAYOUT
|
||||||
|
);
|
||||||
|
const dateGroupConfig = Object.assign({}, RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG, { group: [] });
|
||||||
|
dateGroupConfig.group.push(startDateModel, endDateModel);
|
||||||
|
formModel.push(new DynamicFormGroupModel(dateGroupConfig, RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT));
|
||||||
|
|
||||||
|
this.initModelsValue(formModel);
|
||||||
|
return formModel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting up the form models value
|
||||||
|
*
|
||||||
|
* @return the form models
|
||||||
|
*/
|
||||||
|
initModelsValue(formModel: DynamicFormControlModel[]): DynamicFormControlModel[] {
|
||||||
|
if (this.resourcePolicy) {
|
||||||
|
formModel.forEach((model: any) => {
|
||||||
|
if (model.id === 'date') {
|
||||||
|
if (hasValue(this.resourcePolicy.startDate)) {
|
||||||
|
model.get(0).valueUpdates.next(stringToNgbDateStruct(this.resourcePolicy.startDate));
|
||||||
|
}
|
||||||
|
if (hasValue(this.resourcePolicy.endDate)) {
|
||||||
|
model.get(1).valueUpdates.next(stringToNgbDateStruct(this.resourcePolicy.endDate));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.resourcePolicy.hasOwnProperty(model.id) && this.resourcePolicy[model.id]) {
|
||||||
|
model.valueUpdates.next(this.resourcePolicy[model.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return formModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing If is possible to set policy grant
|
||||||
|
*
|
||||||
|
* @return true if is possible, false otherwise
|
||||||
|
*/
|
||||||
|
canSetGrant(): boolean {
|
||||||
|
return isEmpty(this.resourcePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the eperson or group that will be grant of the permission
|
||||||
|
*
|
||||||
|
* @return the object name
|
||||||
|
*/
|
||||||
|
getResourcePolicyTargetName(): string {
|
||||||
|
return isNotEmpty(this.resourcePolicyGrant) ? this.dsoNameService.getName(this.resourcePolicyGrant) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update reference to the eperson or group that will be grant of the permission
|
||||||
|
*/
|
||||||
|
updateObjectSelected(object: DSpaceObject, isEPerson: boolean): void {
|
||||||
|
this.resourcePolicyGrant = object;
|
||||||
|
this.resourcePolicyGrantType = isEPerson ? 'eperson' : 'group';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on reset
|
||||||
|
* Emit a new reset Event
|
||||||
|
*/
|
||||||
|
onReset(): void {
|
||||||
|
this.reset.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on submit.
|
||||||
|
* Emit a new submit Event whether the form is valid
|
||||||
|
*/
|
||||||
|
onSubmit(): void {
|
||||||
|
this.formService.getFormData(this.formId).pipe(take(1))
|
||||||
|
.subscribe((data) => {
|
||||||
|
const eventPayload: ResourcePolicyEvent = Object.create({});
|
||||||
|
eventPayload.object = this.createResourcePolicyByFormData(data);
|
||||||
|
eventPayload.target = {
|
||||||
|
type: this.resourcePolicyGrantType,
|
||||||
|
uuid: this.resourcePolicyGrant.id
|
||||||
|
};
|
||||||
|
this.submit.emit(eventPayload);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create e new ResourcePolicy by form data
|
||||||
|
*
|
||||||
|
* @return the new ResourcePolicy object
|
||||||
|
*/
|
||||||
|
createResourcePolicyByFormData(data): ResourcePolicy {
|
||||||
|
const resourcePolicy = new ResourcePolicy();
|
||||||
|
resourcePolicy.name = (data.name) ? data.name[0].value : null;
|
||||||
|
resourcePolicy.description = (data.description) ? data.description[0].value : null;
|
||||||
|
resourcePolicy.policyType = (data.policyType) ? data.policyType[0].value : null;
|
||||||
|
resourcePolicy.action = (data.action) ? data.action[0].value : null;
|
||||||
|
resourcePolicy.startDate = (data.date && data.date.start) ? dateToISOFormat(data.date.start[0].value) : null;
|
||||||
|
resourcePolicy.endDate = (data.date && data.date.end) ? dateToISOFormat(data.date.end[0].value) : null;
|
||||||
|
resourcePolicy.type = RESOURCE_POLICY;
|
||||||
|
|
||||||
|
return resourcePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isActive = false;
|
||||||
|
this.formModel = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
DynamicDatePickerModelConfig,
|
||||||
|
DynamicFormControlLayout,
|
||||||
|
DynamicFormGroupModelConfig,
|
||||||
|
DynamicFormOptionConfig,
|
||||||
|
DynamicSelectModelConfig,
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
|
import { DsDynamicInputModelConfig } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
|
import { DsDynamicTextAreaModelConfig } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
|
||||||
|
const policyTypeList: Array<DynamicFormOptionConfig<any>> = [
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_SUBMISSION,
|
||||||
|
value: PolicyType.TYPE_SUBMISSION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_WORKFLOW,
|
||||||
|
value: PolicyType.TYPE_WORKFLOW
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_INHERITED,
|
||||||
|
value: PolicyType.TYPE_INHERITED
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_CUSTOM,
|
||||||
|
value: PolicyType.TYPE_CUSTOM
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const policyActionList: Array<DynamicFormOptionConfig<any>> = [
|
||||||
|
{
|
||||||
|
label: ActionType.READ.toString(),
|
||||||
|
value: ActionType.READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.WRITE.toString(),
|
||||||
|
value: ActionType.WRITE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.REMOVE.toString(),
|
||||||
|
value: ActionType.REMOVE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.ADMIN.toString(),
|
||||||
|
value: ActionType.ADMIN
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DELETE.toString(),
|
||||||
|
value: ActionType.DELETE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.WITHDRAWN_READ.toString(),
|
||||||
|
value: ActionType.WITHDRAWN_READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DEFAULT_BITSTREAM_READ.toString(),
|
||||||
|
value: ActionType.DEFAULT_BITSTREAM_READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DEFAULT_ITEM_READ.toString(),
|
||||||
|
value: ActionType.DEFAULT_ITEM_READ
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_NAME_CONFIG: DsDynamicInputModelConfig = {
|
||||||
|
id: 'name',
|
||||||
|
label: 'resource-policies.form.name.label',
|
||||||
|
metadataFields: [],
|
||||||
|
repeatable: false,
|
||||||
|
submissionId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG: DsDynamicTextAreaModelConfig = {
|
||||||
|
id: 'description',
|
||||||
|
label: 'resource-policies.form.description.label',
|
||||||
|
metadataFields: [],
|
||||||
|
repeatable: false,
|
||||||
|
rows: 10,
|
||||||
|
submissionId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG: DynamicSelectModelConfig<any> = {
|
||||||
|
id: 'policyType',
|
||||||
|
label: 'resource-policies.form.policy-type.label',
|
||||||
|
options: policyTypeList,
|
||||||
|
required: true,
|
||||||
|
validators: {
|
||||||
|
required: null
|
||||||
|
},
|
||||||
|
errorMessages: {
|
||||||
|
required: 'resource-policies.form.policy-type.required'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG: DynamicSelectModelConfig<any> = {
|
||||||
|
id: 'action',
|
||||||
|
label: 'resource-policies.form.action-type.label',
|
||||||
|
options: policyActionList,
|
||||||
|
required: true,
|
||||||
|
validators: {
|
||||||
|
required: null
|
||||||
|
},
|
||||||
|
errorMessages: {
|
||||||
|
required: 'resource-policies.form.action-type.required'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG: DynamicFormGroupModelConfig = {
|
||||||
|
id: 'date',
|
||||||
|
group: []
|
||||||
|
};
|
||||||
|
export const RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
control: 'form-row',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||||
|
id: 'start',
|
||||||
|
label: 'resource-policies.form.date.start.label',
|
||||||
|
placeholder: 'resource-policies.form.date.start.label',
|
||||||
|
inline: false,
|
||||||
|
toggleIcon: 'far fa-calendar-alt'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_START_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
container: 'p-0',
|
||||||
|
label: 'col-form-label'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
host: 'col-md-6'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_END_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||||
|
id: 'end',
|
||||||
|
label: 'resource-policies.form.date.end.label',
|
||||||
|
placeholder: 'resource-policies.form.date.end.label',
|
||||||
|
inline: false,
|
||||||
|
toggleIcon: 'far fa-calendar-alt'
|
||||||
|
};
|
||||||
|
export const RESOURCE_POLICY_FORM_END_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
container: 'p-0',
|
||||||
|
label: 'col-form-label'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
host: 'col-md-6'
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { Injectable, Injector } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { getDataServiceFor } from '../../../core/cache/builders/build-decorators';
|
||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
import { DataService } from '../../../core/data/data.service';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { hasValue, isEmpty } from '../../empty.util';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ResourcePolicyTargetResolver implements Resolve<RemoteData<DSpaceObject>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data service used to make request.
|
||||||
|
*/
|
||||||
|
private dataService: DataService<DSpaceObject>;
|
||||||
|
|
||||||
|
constructor(private parentInjector: Injector, private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving an item based on the parameters in the current route
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
const targetType = route.queryParamMap.get('targetType');
|
||||||
|
const policyTargetId = route.queryParamMap.get('policyTargetId');
|
||||||
|
|
||||||
|
if (isEmpty(targetType) || isEmpty(policyTargetId)) {
|
||||||
|
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = getDataServiceFor(new ResourceType(targetType));
|
||||||
|
this.dataService = Injector.create({
|
||||||
|
providers: [],
|
||||||
|
parent: this.parentInjector
|
||||||
|
}).get(provider);
|
||||||
|
|
||||||
|
return this.dataService.findById(policyTargetId).pipe(
|
||||||
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { hasValue, isEmpty } from '../../empty.util';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { followLink } from '../../utils/follow-link-config.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ResourcePolicyResolver implements Resolve<RemoteData<ResourcePolicy>> {
|
||||||
|
|
||||||
|
constructor(private resourcePolicyService: ResourcePolicyService, private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving an item based on the parameters in the current route
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
const policyId = route.queryParamMap.get('policyId');
|
||||||
|
|
||||||
|
if (isEmpty(policyId)) {
|
||||||
|
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.resourcePolicyService.findById(policyId, followLink('eperson'), followLink('group')).pipe(
|
||||||
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,99 @@
|
|||||||
|
<div *ngIf="(getResourcePolicies() | async)?.length > 0" class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="10">
|
||||||
|
<div class="d-flex justify-content-between align-items-center m-0">
|
||||||
|
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}}
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-danger float-right ml-1"
|
||||||
|
[disabled]="(!(canDelete() | async)) || (isProcessingDelete() | async)"
|
||||||
|
[title]="'resource-policies.delete.btn.title' | translate"
|
||||||
|
(click)="deleteSelectedResourcePolicies()">
|
||||||
|
<span *ngIf="(isProcessingDelete() | async)">
|
||||||
|
<i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!(isProcessingDelete() | async)">
|
||||||
|
<i class='fas fa-trash-alt fa-fw'></i>
|
||||||
|
{{'resource-policies.delete.btn' | translate}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success float-right"
|
||||||
|
[disabled]="(isProcessingDelete() | async)"
|
||||||
|
[title]="'resource-policies.add.for.' + resourceType | translate"
|
||||||
|
(click)="redirectToResourcePolicyCreatePage()">
|
||||||
|
<i class='fas fa-plus'></i>
|
||||||
|
{{'resource-policies.add.button' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="custom-control-input"
|
||||||
|
[id]="'selectAll_' + resourceUUID"
|
||||||
|
(change)="selectAllCheckbox($event)">
|
||||||
|
<label class="custom-control-label" [for]="'selectAll_' + resourceUUID"></label>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>{{'resource-policies.table.headers.id' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.name' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.policyType' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.action' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.eperson' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.group' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.date.start' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.date.end' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let entry of (getResourcePolicies() | async); trackById">
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="custom-control-input"
|
||||||
|
[id]="entry.id"
|
||||||
|
[ngModel]="entry.checked"
|
||||||
|
(ngModelChange)="selectCheckbox(entry, $event)">
|
||||||
|
<label class="custom-control-label" [for]="entry.id"></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<th scope="row">
|
||||||
|
{{entry.id}}
|
||||||
|
</th>
|
||||||
|
<td>{{entry.policy.name}}</td>
|
||||||
|
<td>{{entry.policy.policyType}}</td>
|
||||||
|
<td>{{entry.policy.action}}</td>
|
||||||
|
<td *ngIf="(hasEPerson(entry.policy) | async)">
|
||||||
|
{{getEPersonName(entry.policy) | async}}
|
||||||
|
</td>
|
||||||
|
<td *ngIf="!(hasEPerson(entry.policy) | async)"></td>
|
||||||
|
<td *ngIf="(hasGroup(entry.policy) | async)">
|
||||||
|
{{getGroupName(entry.policy) | async}}
|
||||||
|
</td>
|
||||||
|
<td *ngIf="!(hasGroup(entry.policy) | async)"></td>
|
||||||
|
<td>{{formatDate(entry.policy.startDate)}}</td>
|
||||||
|
<td>{{formatDate(entry.policy.endDate)}}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button class="btn btn-outline-primary btn-sm"
|
||||||
|
[title]="'resource-policies.table.headers.edit.policy' | translate"
|
||||||
|
(click)="redirectToResourcePolicyEditPage(entry.policy)">
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(hasGroup(entry.policy) | async)" class="btn btn-outline-primary btn-sm"
|
||||||
|
[title]="'resource-policies.table.headers.edit.group' | translate"
|
||||||
|
(click)="redirectToGroupEditPage(entry.policy)">
|
||||||
|
<i class="fas fa-users fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
td .btn-link:focus {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
@@ -0,0 +1,485 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
|
import { Bundle } from '../../core/shared/bundle.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../../+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { LinkService } from '../../core/cache/builders/link.service';
|
||||||
|
import { getMockLinkService } from '../mocks/link-service.mock';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../mocks/mock-resource-policy-service';
|
||||||
|
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../mocks/request.service.mock';
|
||||||
|
import { RouterStub } from '../testing/router.stub';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { ResourcePoliciesComponent } from './resource-policies.component';
|
||||||
|
import { PolicyType } from '../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../core/resource-policy/models/action-type.model';
|
||||||
|
import { EPersonMock } from '../testing/eperson.mock';
|
||||||
|
import { GroupMock } from '../testing/group-mock';
|
||||||
|
|
||||||
|
describe('ResourcePoliciesComponent test suite', () => {
|
||||||
|
let comp: ResourcePoliciesComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePoliciesComponent>;
|
||||||
|
let de;
|
||||||
|
let routerStub: any;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
const notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const anotherResourcePolicy: any = {
|
||||||
|
id: '2',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-2',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject(EPersonMock)),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject({}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream1',
|
||||||
|
uuid: 'bitstream1'
|
||||||
|
});
|
||||||
|
const bitstream2 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream2',
|
||||||
|
uuid: 'bitstream2'
|
||||||
|
});
|
||||||
|
const bitstream3 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream3',
|
||||||
|
uuid: 'bitstream3'
|
||||||
|
});
|
||||||
|
const bitstream4 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream4',
|
||||||
|
uuid: 'bitstream4'
|
||||||
|
});
|
||||||
|
const bundle1 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle1',
|
||||||
|
uuid: 'bundle1',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle1-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
|
||||||
|
});
|
||||||
|
const bundle2 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle2',
|
||||||
|
uuid: 'bundle2',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle2-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4])
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'itemUUID',
|
||||||
|
id: 'itemUUID',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([bundle1, bundle2])
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const epersonService = jasmine.createSpyObj('epersonService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupService = jasmine.createSpyObj('groupService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
});
|
||||||
|
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
const getInitEntries = () => {
|
||||||
|
return [
|
||||||
|
Object.assign({}, {
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
policy: resourcePolicy,
|
||||||
|
checked: false
|
||||||
|
}),
|
||||||
|
Object.assign({}, {
|
||||||
|
id: anotherResourcePolicy.id,
|
||||||
|
policy: anotherResourcePolicy,
|
||||||
|
checked: false
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourcePolicySelectedEntries = [
|
||||||
|
{
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
policy: resourcePolicy,
|
||||||
|
checked: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: anotherResourcePolicy.id,
|
||||||
|
policy: anotherResourcePolicy,
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
const array = [resourcePolicy, anotherResourcePolicy];
|
||||||
|
const paginatedList = new PaginatedList(pageInfo, array);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePoliciesComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: EPersonDataService, useValue: epersonService },
|
||||||
|
{ provide: GroupDataService, useValue: groupService },
|
||||||
|
{ provide: NotificationsService, useValue: notificationsServiceStub },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ResourcePoliciesComponent
|
||||||
|
], schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policies [resourceUUID]="resourceUUID" [resourceType]="resourceType"></ds-resource-policies>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePoliciesComponent', inject([ResourcePoliciesComponent], (app: ResourcePoliciesComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResourcePoliciesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
spyOn(comp, 'initResourcePolicyLIst');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.isActive).toBeTruthy();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init resource policies list properly', () => {
|
||||||
|
const expected = getInitEntries();
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
resourcePolicyService.searchByResource.and.returnValue(hot('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
}));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.initResourcePolicyLIst());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePoliciesEntries$.value).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResourcePoliciesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(initResourcePolicyEntries);
|
||||||
|
resourcePolicyService.searchByResource.and.returnValue(observableOf({}));
|
||||||
|
spyOn(comp, 'initResourcePolicyLIst').and.callFake(() => ({}));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('canDelete', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(initResourcePolicyEntries);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when no row is selected', () => {
|
||||||
|
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when al least is selected', () => {
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a table with a row for each policy', () => {
|
||||||
|
const rows = fixture.debugElement.queryAll(By.css('table > tbody > tr'));
|
||||||
|
expect(rows.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteSelectedResourcePolicies', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(resourcePolicySelectedEntries);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when delete is successful', () => {
|
||||||
|
|
||||||
|
resourcePolicyService.delete.and.returnValue(observableOf(true));
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.success).toHaveBeenCalled();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when delete is not successful', () => {
|
||||||
|
|
||||||
|
resourcePolicyService.delete.and.returnValue(observableOf(false));
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.error).toHaveBeenCalled();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the resource\'s policy list', () => {
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
expect(comp.getResourcePolicies()).toBeObservable(cold('a', {
|
||||||
|
a: initResourcePolicyEntries
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasEPerson', () => {
|
||||||
|
it('should true when policy is link to the eperson', () => {
|
||||||
|
|
||||||
|
expect(comp.hasEPerson(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: false,
|
||||||
|
b: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should false when policy is not link to the eperson', () => {
|
||||||
|
|
||||||
|
expect(comp.hasEPerson(resourcePolicy)).toBeObservable(cold('(aa|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasGroup', () => {
|
||||||
|
it('should true when policy is link to the group', () => {
|
||||||
|
|
||||||
|
expect(comp.hasGroup(resourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: false,
|
||||||
|
b: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should false when policy is not link to the group', () => {
|
||||||
|
|
||||||
|
expect(comp.hasGroup(anotherResourcePolicy)).toBeObservable(cold('(aa|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getEPersonName', () => {
|
||||||
|
it('should return the eperson name', () => {
|
||||||
|
|
||||||
|
expect(comp.getEPersonName(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: '',
|
||||||
|
b: 'User Test'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGroupName', () => {
|
||||||
|
it('should return the group name', () => {
|
||||||
|
|
||||||
|
expect(comp.getGroupName(resourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: '',
|
||||||
|
b: 'testgroupname'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format date properly', () => {
|
||||||
|
expect(comp.formatDate('2020-04-14T12:00:00Z')).toBe('2020-04-14');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select All Checkbox', () => {
|
||||||
|
spyOn(comp, 'selectAllCheckbox').and.callThrough();
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > thead > tr:nth-child(2) input'));
|
||||||
|
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.selectAllCheckbox).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select a Checkbox', () => {
|
||||||
|
spyOn(comp, 'selectCheckbox').and.callThrough();
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
|
||||||
|
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.selectCheckbox).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to create resource policy page', () => {
|
||||||
|
|
||||||
|
comp.redirectToResourcePolicyCreatePage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to resource policy edit page', () => {
|
||||||
|
|
||||||
|
comp.redirectToResourcePolicyEditPage(resourcePolicy);
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to resource policy edit page', () => {
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
|
||||||
|
comp.redirectToGroupEditPage(resourcePolicy);
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
resourceUUID = 'itemUUID';
|
||||||
|
resourceType = 'item';
|
||||||
|
}
|
345
src/app/shared/resource-policies/resource-policies.component.ts
Normal file
345
src/app/shared/resource-policies/resource-policies.component.ts
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, from as observableFrom, Observable, Subscription } from 'rxjs';
|
||||||
|
import { concatMap, distinctUntilChanged, filter, map, reduce, scan, startWith, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
import {
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../../core/shared/operators';
|
||||||
|
import { ResourcePolicy } from '../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { Group } from '../../core/eperson/models/group.model';
|
||||||
|
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
|
||||||
|
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||||
|
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
import { dateToString, stringToNgbDateStruct } from '../date.util';
|
||||||
|
import { followLink } from '../utils/follow-link-config.model';
|
||||||
|
import { ADMIN_MODULE_PATH } from '../../app-routing.module';
|
||||||
|
import { ACCESS_CONTROL_MODULE_PATH } from '../../+admin/admin-routing.module';
|
||||||
|
import { GROUP_EDIT_PATH } from '../../+admin/admin-access-control/admin-access-control-routing.module';
|
||||||
|
|
||||||
|
interface ResourcePolicyCheckboxEntry {
|
||||||
|
id: string;
|
||||||
|
policy: ResourcePolicy;
|
||||||
|
checked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policies',
|
||||||
|
styleUrls: ['./resource-policies.component.scss'],
|
||||||
|
templateUrl: './resource-policies.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that shows the policies for given resource
|
||||||
|
*/
|
||||||
|
export class ResourcePoliciesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource UUID
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
@Input() public resourceUUID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type (e.g. 'item', 'bundle' etc) used as key to build automatically translation label
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
@Input() public resourceType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if component is active
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private isActive: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission delete operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processingDelete$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of policies for given resource
|
||||||
|
* @type {BehaviorSubject<ResourcePolicyCheckboxEntry[]>}
|
||||||
|
*/
|
||||||
|
private resourcePoliciesEntries$: BehaviorSubject<ResourcePolicyCheckboxEntry[]> =
|
||||||
|
new BehaviorSubject<ResourcePolicyCheckboxEntry[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {ChangeDetectorRef} cdr
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {EPersonDataService} ePersonService
|
||||||
|
* @param {GroupDataService} groupService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private ePersonService: EPersonDataService,
|
||||||
|
private groupService: GroupDataService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the resource's policies
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isActive = true;
|
||||||
|
this.initResourcePolicyLIst();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any selected resource's policies to be deleted
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
canDelete(): Observable<boolean> {
|
||||||
|
return observableFrom(this.resourcePoliciesEntries$.value).pipe(
|
||||||
|
filter((entry: ResourcePolicyCheckboxEntry) => entry.checked),
|
||||||
|
reduce((acc: any, value: any) => [...acc, ...value], []),
|
||||||
|
map((entries: ResourcePolicyCheckboxEntry[]) => isNotEmpty(entries)),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected resource's policies
|
||||||
|
*/
|
||||||
|
deleteSelectedResourcePolicies(): void {
|
||||||
|
this.processingDelete$.next(true);
|
||||||
|
const policiesToDelete: ResourcePolicyCheckboxEntry[] = this.resourcePoliciesEntries$.value
|
||||||
|
.filter((entry: ResourcePolicyCheckboxEntry) => entry.checked);
|
||||||
|
this.subs.push(
|
||||||
|
observableFrom(policiesToDelete).pipe(
|
||||||
|
concatMap((entry: ResourcePolicyCheckboxEntry) => this.resourcePolicyService.delete(entry.policy.id)),
|
||||||
|
scan((acc: any, value: any) => [...acc, ...value], []),
|
||||||
|
filter((results: boolean[]) => results.length === policiesToDelete.length),
|
||||||
|
take(1),
|
||||||
|
).subscribe((results: boolean[]) => {
|
||||||
|
const failureResults = results.filter((result: boolean) => !result);
|
||||||
|
if (isEmpty(failureResults)) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.delete.success.content'));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.delete.failure.content'));
|
||||||
|
}
|
||||||
|
this.initResourcePolicyLIst();
|
||||||
|
this.processingDelete$.next(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date in simplified format (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* @return a string with formatted date
|
||||||
|
*/
|
||||||
|
formatDate(date: string): string {
|
||||||
|
return isNotEmpty(date) ? dateToString(stringToNgbDateStruct(date)) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ePerson's name which the given policy is linked to
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
getEPersonName(policy: ResourcePolicy): Observable<string> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
|
||||||
|
return policy.eperson.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
|
||||||
|
startWith('')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the group's name which the given policy is linked to
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
getGroupName(policy: ResourcePolicy): Observable<string> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
return policy.group.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((group: Group) => this.dsoNameService.getName(group)),
|
||||||
|
startWith('')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all resource's policies
|
||||||
|
*
|
||||||
|
* @return an observable that emits all resource's policies
|
||||||
|
*/
|
||||||
|
getResourcePolicies(): Observable<ResourcePolicyCheckboxEntry[]> {
|
||||||
|
return this.resourcePoliciesEntries$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given policy is linked to a ePerson
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
* @return an observable that emits true when the policy is linked to a ePerson, false otherwise
|
||||||
|
*/
|
||||||
|
hasEPerson(policy): Observable<boolean> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
|
||||||
|
return policy.eperson.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((eperson: EPerson) => isNotEmpty(eperson)),
|
||||||
|
startWith(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given policy is linked to a group
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
* @return an observable that emits true when the policy is linked to a group, false otherwise
|
||||||
|
*/
|
||||||
|
hasGroup(policy): Observable<boolean> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
return policy.group.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((group: Group) => isNotEmpty(group)),
|
||||||
|
startWith(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the resource's policies list
|
||||||
|
*/
|
||||||
|
initResourcePolicyLIst() {
|
||||||
|
this.resourcePolicyService.searchByResource(this.resourceUUID, null,
|
||||||
|
followLink('eperson'), followLink('group')).pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((result) => {
|
||||||
|
const entries = result.payload.page.map((policy: ResourcePolicy) => ({
|
||||||
|
id: policy.id,
|
||||||
|
policy: policy,
|
||||||
|
checked: false
|
||||||
|
}));
|
||||||
|
this.resourcePoliciesEntries$.next(entries);
|
||||||
|
// Remove cached request
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourceUUID);
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if a delete operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessingDelete(): Observable<boolean> {
|
||||||
|
return this.processingDelete$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to resource policy creation page
|
||||||
|
*/
|
||||||
|
redirectToResourcePolicyCreatePage(): void {
|
||||||
|
this.router.navigate([`../create`], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
policyTargetId: this.resourceUUID,
|
||||||
|
targetType: this.resourceType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to resource policy editing page
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
redirectToResourcePolicyEditPage(policy: ResourcePolicy): void {
|
||||||
|
this.router.navigate([`../edit`], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
policyId: policy.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to group edit page
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
redirectToGroupEditPage(policy: ResourcePolicy): void {
|
||||||
|
this.requestService.removeByHrefSubstring(policy._links.group.href);
|
||||||
|
this.subs.push(
|
||||||
|
this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((group: Group) => group.id)
|
||||||
|
).subscribe((groupUUID) => {
|
||||||
|
this.router.navigate([ADMIN_MODULE_PATH, ACCESS_CONTROL_MODULE_PATH, GROUP_EDIT_PATH, groupUUID])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/unselect all checkbox in the list
|
||||||
|
*/
|
||||||
|
selectAllCheckbox(event: any): void {
|
||||||
|
const checked = event.target.checked;
|
||||||
|
this.resourcePoliciesEntries$.value.forEach((entry: ResourcePolicyCheckboxEntry) => entry.checked = checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/unselect checkbox
|
||||||
|
*/
|
||||||
|
selectCheckbox(policyEntry: ResourcePolicyCheckboxEntry, checked: boolean) {
|
||||||
|
policyEntry.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isActive = false;
|
||||||
|
this.resourcePoliciesEntries$ = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -194,6 +194,14 @@ import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-ta
|
|||||||
import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive';
|
import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive';
|
||||||
import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component';
|
import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component';
|
||||||
import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component';
|
import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component';
|
||||||
|
import { ResourcePoliciesComponent } from './resource-policies/resource-policies.component';
|
||||||
|
import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive';
|
||||||
|
import { ResourcePolicyFormComponent } from './resource-policies/form/resource-policy-form.component';
|
||||||
|
import { EpersonGroupListComponent } from './resource-policies/form/eperson-group-list/eperson-group-list.component';
|
||||||
|
import { ResourcePolicyTargetResolver } from './resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyResolver } from './resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { EpersonSearchBoxComponent } from './resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component';
|
||||||
|
import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -373,7 +381,12 @@ const COMPONENTS = [
|
|||||||
PublicationSearchResultListElementComponent,
|
PublicationSearchResultListElementComponent,
|
||||||
ItemVersionsNoticeComponent,
|
ItemVersionsNoticeComponent,
|
||||||
ModifyItemOverviewComponent,
|
ModifyItemOverviewComponent,
|
||||||
ImpersonateNavbarComponent
|
ImpersonateNavbarComponent,
|
||||||
|
ResourcePoliciesComponent,
|
||||||
|
ResourcePolicyFormComponent,
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
EpersonSearchBoxComponent,
|
||||||
|
GroupSearchBoxComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -461,7 +474,9 @@ const PROVIDERS = [
|
|||||||
{
|
{
|
||||||
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
||||||
useValue: dsDynamicFormControlMapFn
|
useValue: dsDynamicFormControlMapFn
|
||||||
}
|
},
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIRECTIVES = [
|
const DIRECTIVES = [
|
||||||
@@ -475,7 +490,8 @@ const DIRECTIVES = [
|
|||||||
RoleDirective,
|
RoleDirective,
|
||||||
MetadataRepresentationDirective,
|
MetadataRepresentationDirective,
|
||||||
ListableObjectDirective,
|
ListableObjectDirective,
|
||||||
ClaimedTaskActionsDirective
|
ClaimedTaskActionsDirective,
|
||||||
|
NgForTrackByIdDirective
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -150,4 +150,8 @@ export class AuthServiceStub {
|
|||||||
getImpersonateID() {
|
getImpersonateID() {
|
||||||
return this.impersonating;
|
return this.impersonating;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetAuthenticationError() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ export const GroupMock2: Group = Object.assign(new Group(), {
|
|||||||
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/subgroups' },
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/subgroups' },
|
||||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
||||||
},
|
},
|
||||||
|
_name: 'testgroupname2',
|
||||||
id: 'testgroupid2',
|
id: 'testgroupid2',
|
||||||
uuid: 'testgroupid2',
|
uuid: 'testgroupid2',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
@@ -32,6 +33,7 @@ export const GroupMock: Group = Object.assign(new Group(), {
|
|||||||
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/subgroups' },
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/subgroups' },
|
||||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
||||||
},
|
},
|
||||||
|
_name: 'testgroupname',
|
||||||
id: 'testgroupid',
|
id: 'testgroupid',
|
||||||
uuid: 'testgroupid',
|
uuid: 'testgroupid',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
|
@@ -3,7 +3,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||||||
import { find } from 'rxjs/operators';
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { ResourcePolicy } from '../../../../core/shared/resource-policy.model';
|
import { ResourcePolicy } from '../../../../core/resource-policy/models/resource-policy.model';
|
||||||
import { isEmpty } from '../../../../shared/empty.util';
|
import { isEmpty } from '../../../../shared/empty.util';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -42,7 +42,7 @@ export class SubmissionSectionUploadAccessConditionsComponent implements OnInit
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
|
this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
|
||||||
if (isEmpty(accessCondition.name)) {
|
if (isEmpty(accessCondition.name)) {
|
||||||
this.groupService.findById(accessCondition.groupUUID).pipe(
|
this.groupService.findByHref(accessCondition._links.group.href).pipe(
|
||||||
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded))
|
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded))
|
||||||
.subscribe((rd: RemoteData<Group>) => {
|
.subscribe((rd: RemoteData<Group>) => {
|
||||||
const group: Group = rd.payload;
|
const group: Group = rd.payload;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
@@ -20,19 +23,17 @@ import {
|
|||||||
mockSubmissionId,
|
mockSubmissionId,
|
||||||
mockSubmissionState,
|
mockSubmissionState,
|
||||||
mockUploadConfigResponse,
|
mockUploadConfigResponse,
|
||||||
mockUploadConfigResponseNotRequired, mockUploadFiles,
|
mockUploadConfigResponseNotRequired,
|
||||||
|
mockUploadFiles,
|
||||||
} from '../../../shared/mocks/submission.mock';
|
} from '../../../shared/mocks/submission.mock';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
||||||
import { SectionUploadService } from './section-upload.service';
|
import { SectionUploadService } from './section-upload.service';
|
||||||
import { SubmissionSectionUploadComponent } from './section-upload.component';
|
import { SubmissionSectionUploadComponent } from './section-upload.component';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { ResourcePolicy } from '../../../core/shared/resource-policy.model';
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
import { ResourcePolicyService } from '../../../core/data/resource-policy.service';
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
import { ConfigData } from '../../../core/config/config-data';
|
import { ConfigData } from '../../../core/config/config-data';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { Group } from '../../../core/eperson/models/group.model';
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
@@ -8,7 +8,7 @@ import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shar
|
|||||||
import { SectionUploadService } from './section-upload.service';
|
import { SectionUploadService } from './section-upload.service';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { ResourcePolicyService } from '../../../core/data/resource-policy.service';
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
||||||
import { SubmissionUploadsModel } from '../../../core/config/models/config-submission-uploads.model';
|
import { SubmissionUploadsModel } from '../../../core/config/models/config-submission-uploads.model';
|
||||||
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
||||||
|
@@ -996,6 +996,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.",
|
||||||
|
|
||||||
|
"item.edit.authorizations.title": "Edit item's Policies",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"item.bitstreams.upload.bundle": "Bundle",
|
"item.bitstreams.upload.bundle": "Bundle",
|
||||||
|
|
||||||
@@ -2066,6 +2071,100 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"resource-policies.add.button": "Add",
|
||||||
|
|
||||||
|
"resource-policies.add.for.": "Add a new policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.bitstream": "Add a new Bitstream policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.bundle": "Add a new Bundle policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.item": "Add a new Item policy",
|
||||||
|
|
||||||
|
"resource-policies.create.page.heading": "Create new resource policy for ",
|
||||||
|
|
||||||
|
"resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.",
|
||||||
|
|
||||||
|
"resource-policies.create.page.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.create.page.title": "Create new resource policy",
|
||||||
|
|
||||||
|
"resource-policies.delete.btn": "Delete selected",
|
||||||
|
|
||||||
|
"resource-policies.delete.btn.title": "Delete selected resource policies",
|
||||||
|
|
||||||
|
"resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.",
|
||||||
|
|
||||||
|
"resource-policies.delete.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.heading": "Edit resource policy ",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.title": "Edit resource policy",
|
||||||
|
|
||||||
|
"resource-policies.form.action-type.label": "Select the action type",
|
||||||
|
|
||||||
|
"resource-policies.form.action-type.required": "You must select the resource policy action.",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.label": "The eperson or group that will be grant of the permission",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.select.btn": "Select",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.tab.eperson": "Search for a ePerson",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.tab.group": "Search for a group",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.action": "Action",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.name": "Name",
|
||||||
|
|
||||||
|
"resource-policies.form.date.end.label": "End Date",
|
||||||
|
|
||||||
|
"resource-policies.form.date.start.label": "Start Date",
|
||||||
|
|
||||||
|
"resource-policies.form.description.label": "Description",
|
||||||
|
|
||||||
|
"resource-policies.form.name.label": "Name",
|
||||||
|
|
||||||
|
"resource-policies.form.policy-type.label": "Select the policy type",
|
||||||
|
|
||||||
|
"resource-policies.form.policy-type.required": "You must select the resource policy type.",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.action": "Action",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.date.end": "End Date",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.date.start": "Start Date",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit": "Edit",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit.group": "Edit group",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit.policy": "Edit policy",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.eperson": "EPerson",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.group": "Group",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.id": "ID",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.name": "Name",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.policyType": "type",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.bundle": "Policies for Bundle",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.item": "Policies for Item",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"search.description": "",
|
"search.description": "",
|
||||||
|
|
||||||
"search.switch-configuration.title": "Show",
|
"search.switch-configuration.title": "Show",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -160,6 +160,10 @@ export const environment: GlobalConfig = {
|
|||||||
code: 'lv',
|
code: 'lv',
|
||||||
label: 'Latviešu',
|
label: 'Latviešu',
|
||||||
active: true,
|
active: true,
|
||||||
|
},{
|
||||||
|
code: 'fi',
|
||||||
|
label: 'Suomi',
|
||||||
|
active: true,
|
||||||
}],
|
}],
|
||||||
// Browse-By Pages
|
// Browse-By Pages
|
||||||
browseBy: {
|
browseBy: {
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
export const ROUTES: string[] = [
|
|
||||||
'home',
|
|
||||||
'items/:id',
|
|
||||||
'login',
|
|
||||||
'logout',
|
|
||||||
'collections/:id',
|
|
||||||
'communities/:id',
|
|
||||||
'login',
|
|
||||||
'logout',
|
|
||||||
'search',
|
|
||||||
'submit',
|
|
||||||
'workspaceitems/:id/edit',
|
|
||||||
'workflowitems/:id/edit',
|
|
||||||
'**'
|
|
||||||
];
|
|
@@ -8,7 +8,7 @@ $sidebar-items-width: 250px !default;
|
|||||||
$total-sidebar-width: $collapsed-sidebar-width + $sidebar-items-width !default;
|
$total-sidebar-width: $collapsed-sidebar-width + $sidebar-items-width !default;
|
||||||
|
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
$fa-font-path: "node_modules/@fortawesome/fontawesome-free/webfonts" !default;
|
$fa-font-path: "/assets/fonts" !default;
|
||||||
/* Images */
|
/* Images */
|
||||||
$image-path: "../assets/images" !default;
|
$image-path: "../assets/images" !default;
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"extends": "./tsconfig.app.json",
|
"extends": "./tsconfig.app.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./out-tsc/app-server",
|
"outDir": "./out-tsc/app-server",
|
||||||
"module": "commonjs"
|
"module": "commonjs",
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"entryModule": "./src/modules/app/server-app.module#ServerAppModule"
|
"entryModule": "./src/modules/app/server-app.module#ServerAppModule"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user