mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'origin/main' into CST-4506_item_embargo
# Conflicts: # src/app/core/core.module.ts # src/app/submission/objects/submission-objects.effects.ts # src/app/submission/submission.module.ts
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,10 +7,6 @@ npm-debug.log
|
|||||||
|
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
/src/environments/environment.ts
|
|
||||||
/src/environments/environment.dev.ts
|
|
||||||
/src/environments/environment.prod.ts
|
|
||||||
|
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
|
179
README.md
179
README.md
@@ -102,48 +102,87 @@ Installing
|
|||||||
|
|
||||||
### Configuring
|
### Configuring
|
||||||
|
|
||||||
Default configuration file is located in `src/environments/` folder.
|
Default configuration file is located in `config/` folder.
|
||||||
|
|
||||||
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.
|
To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point.
|
||||||
|
|
||||||
- Create a new `environment.dev.ts` file in `src/environments/` for a `development` environment;
|
- Create a new `config.(dev or development).yml` file in `config/` for a `development` environment;
|
||||||
- Create a new `environment.prod.ts` file in `src/environments/` for a `production` environment;
|
- Create a new `config.(prod or production).yml` file in `config/` for a `production` environment;
|
||||||
|
|
||||||
The server settings can also be overwritten using an environment file.
|
The settings can also be overwritten using an environment file or environment variables.
|
||||||
|
|
||||||
This file should be called `.env` and be placed in the project root.
|
This file should be called `.env` and be placed in the project root.
|
||||||
|
|
||||||
The following settings can be overwritten in this file:
|
The following non-convention settings:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DSPACE_HOST # The host name of the angular application
|
DSPACE_HOST # The host name of the angular application
|
||||||
DSPACE_PORT # The port number of the angular application
|
DSPACE_PORT # The port number of the angular application
|
||||||
DSPACE_NAMESPACE # The namespace of the angular application
|
DSPACE_NAMESPACE # The namespace of the angular application
|
||||||
DSPACE_SSL # Whether the angular application uses SSL [true/false]
|
DSPACE_SSL # Whether the angular application uses SSL [true/false]
|
||||||
|
```
|
||||||
|
|
||||||
DSPACE_REST_HOST # The host name of the REST application
|
All other settings can be set using the following convention for naming the environment variables:
|
||||||
DSPACE_REST_PORT # The port number of the REST application
|
|
||||||
DSPACE_REST_NAMESPACE # The namespace of the REST application
|
1. replace all `.` with `_`
|
||||||
DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false]
|
2. convert all characters to upper case
|
||||||
|
3. prefix with `DSPACE_`
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# The host name of the REST application
|
||||||
|
rest.host => DSPACE_REST_HOST
|
||||||
|
|
||||||
|
# The port number of the REST application
|
||||||
|
rest.port => DSPACE_REST_PORT
|
||||||
|
|
||||||
|
# The namespace of the REST application
|
||||||
|
rest.nameSpace => DSPACE_REST_NAMESPACE
|
||||||
|
|
||||||
|
# Whether the angular REST uses SSL [true/false]
|
||||||
|
rest.ssl => DSPACE_REST_SSL
|
||||||
|
|
||||||
|
cache.msToLive.default => DSPACE_CACHE_MSTOLIVE_DEFAULT
|
||||||
|
auth.ui.timeUntilIdle => DSPACE_AUTH_UI_TIMEUNTILIDLE
|
||||||
|
```
|
||||||
|
|
||||||
|
The equavelant to the non-conventional legacy settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DSPACE_UI_HOST => DSPACE_HOST
|
||||||
|
DSPACE_UI_PORT => DSPACE_PORT
|
||||||
|
DSPACE_UI_NAMESPACE => DSPACE_NAMESPACE
|
||||||
|
DSPACE_UI_SSL => DSPACE_SSL
|
||||||
```
|
```
|
||||||
|
|
||||||
The same settings can also be overwritten by setting system environment variables instead, E.g.:
|
The same settings can also be overwritten by setting system environment variables instead, E.g.:
|
||||||
```bash
|
```bash
|
||||||
export DSPACE_HOST=api7.dspace.org
|
export DSPACE_HOST=api7.dspace.org
|
||||||
|
export DSPACE_UI_PORT=4200
|
||||||
```
|
```
|
||||||
|
|
||||||
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides **`environment.(prod, dev or test).ts`** overrides **`environment.common.ts`**
|
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `DSPACE_APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`**
|
||||||
|
|
||||||
These configuration sources are collected **at build time**, and written to `src/environments/environment.ts`. At runtime the configuration is fixed, and neither `.env` nor the process' environment will be consulted.
|
These configuration sources are collected **at run time**, and written to `dist/browser/assets/config.json` for production and `src/app/assets/config.json` for development.
|
||||||
|
|
||||||
|
The configuration file can be externalized by using environment variable `DSPACE_APP_CONFIG_PATH`.
|
||||||
|
|
||||||
#### Using environment variables in code
|
#### Using environment variables in code
|
||||||
To use environment variables in a UI component, use:
|
To use environment variables in a UI component, use:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { environment } from '../environment.ts';
|
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
...
|
||||||
|
constructor(@Inject(APP_CONFIG) private appConfig: AppConfig) {}
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
This file is generated by the script located in `scripts/set-env.ts`. This script will run automatically before every build, or can be manually triggered using the appropriate `config` script in `package.json`
|
or
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { environment } from '../environment.ts';
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Running the app
|
Running the app
|
||||||
@@ -231,7 +270,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con
|
|||||||
The test files can be found in the `./cypress/integration/` folder.
|
The test files can be found in the `./cypress/integration/` folder.
|
||||||
|
|
||||||
Before you can run e2e tests, two things are required:
|
Before you can run e2e tests, two things are required:
|
||||||
1. You MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `environment.prod.ts` or `environment.common.ts`. You may override this using env variables, see [Configuring](#configuring).
|
1. You MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring).
|
||||||
2. Your backend MUST include our Entities Test Data set. Some tests run against a (currently hardcoded) Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data
|
2. Your backend MUST include our Entities Test Data set. Some tests run against a (currently hardcoded) Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data
|
||||||
|
|
||||||
Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results.
|
Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results.
|
||||||
@@ -311,49 +350,85 @@ File Structure
|
|||||||
|
|
||||||
```
|
```
|
||||||
dspace-angular
|
dspace-angular
|
||||||
├── README.md * This document
|
├── config *
|
||||||
├── app.yaml * Application manifest file
|
│ └── config.yml * Default app config
|
||||||
├── config * Folder for configuration files
|
|
||||||
│ ├── environment.default.js * Default configuration files
|
|
||||||
│ └── environment.test.js * Test configuration files
|
|
||||||
├── docs * Folder for documentation
|
|
||||||
├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests
|
├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests
|
||||||
│ ├── integration * Folder for e2e/integration test files
|
│ ├── downloads *
|
||||||
│ ├── fixtures * Folder for any fixtures needed by e2e tests
|
│ ├── fixtures * Folder for e2e/integration test files
|
||||||
│ ├── plugins * Folder for Cypress plugins (if any)
|
│ ├── integration * Folder for any fixtures needed by e2e tests
|
||||||
│ ├── support * Folder for global e2e test actions/commands (run for all tests)
|
│ ├── plugins * Folder for Cypress plugins (if any)
|
||||||
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
│ ├── support * Folder for global e2e test actions/commands (run for all tests)
|
||||||
|
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
||||||
|
├── docker * See docker/README.md for details
|
||||||
|
│ ├── cli.assetstore.yml *
|
||||||
|
│ ├── cli.ingest.yml *
|
||||||
|
│ ├── cli.yml *
|
||||||
|
│ ├── db.entities.yml *
|
||||||
|
│ ├── docker-compose-ci.yml *
|
||||||
|
│ ├── docker-compose-rest.yml *
|
||||||
|
│ ├── docker-compose.yml *
|
||||||
|
│ └── README.md *
|
||||||
|
├── docs * Folder for documentation
|
||||||
|
│ └── Configuration.md * Configuration documentation
|
||||||
|
├── scripts *
|
||||||
|
│ ├── merge-i18n-files.ts *
|
||||||
|
│ ├── serve.ts *
|
||||||
|
│ ├── sync-i18n-files.ts *
|
||||||
|
│ ├── test-rest.ts *
|
||||||
|
│ └── webpack.js *
|
||||||
|
├── src * The source of the application
|
||||||
|
│ ├── app * The source code of the application, subdivided by module/page.
|
||||||
|
│ ├── assets * Folder for static resources
|
||||||
|
│ │ ├── fonts * Folder for fonts
|
||||||
|
│ │ ├── i18n * Folder for i18n translations
|
||||||
|
│ │ └── images * Folder for images
|
||||||
|
│ ├── backend * Folder containing a mock of the REST API, hosted by the express server
|
||||||
|
│ ├── config *
|
||||||
|
│ ├── environments *
|
||||||
|
│ │ ├── environment.production.ts * Production configuration files
|
||||||
|
│ │ ├── environment.test.ts * Test configuration files
|
||||||
|
│ │ └── environment.ts * Default (development) configuration files
|
||||||
|
│ ├── mirador-viewer *
|
||||||
|
│ ├── modules *
|
||||||
|
│ ├── ngx-translate-loaders *
|
||||||
|
│ ├── styles * Folder containing global styles
|
||||||
|
│ ├── themes * Folder containing available themes
|
||||||
|
│ │ ├── custom * Template folder for creating a custom theme
|
||||||
|
│ │ └── dspace * Default 'dspace' theme
|
||||||
|
│ ├── index.csr.html * The index file for client side rendering fallback
|
||||||
|
│ ├── index.html * The index file
|
||||||
|
│ ├── main.browser.ts * The bootstrap file for the client
|
||||||
|
│ ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server
|
||||||
|
│ ├── polyfills.ts *
|
||||||
|
│ ├── robots.txt * The robots.txt file
|
||||||
|
│ ├── test.ts *
|
||||||
|
│ └── typings.d.ts *
|
||||||
|
├── webpack *
|
||||||
|
│ ├── helpers.ts * Webpack helpers
|
||||||
|
│ ├── webpack.browser.ts * Webpack (https://webpack.github.io/) config for browser build
|
||||||
|
│ ├── webpack.common.ts * Webpack (https://webpack.github.io/) common build config
|
||||||
|
│ ├── webpack.mirador.config.ts * Webpack (https://webpack.github.io/) config for mirador config build
|
||||||
|
│ ├── webpack.prod.ts * Webpack (https://webpack.github.io/) config for prod build
|
||||||
|
│ └── webpack.test.ts * Webpack (https://webpack.github.io/) config for test build
|
||||||
|
├── angular.json * Angular CLI (https://angular.io/cli) configuration
|
||||||
|
├── cypress.json * Cypress Test (https://www.cypress.io/) configuration
|
||||||
|
├── Dockerfile *
|
||||||
├── karma.conf.js * Karma configuration file for Unit Test
|
├── karma.conf.js * Karma configuration file for Unit Test
|
||||||
|
├── LICENSE *
|
||||||
|
├── LICENSES_THIRD_PARTY *
|
||||||
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
||||||
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
||||||
├── postcss.config.js * PostCSS (http://postcss.org/) configuration file
|
├── postcss.config.js * PostCSS (http://postcss.org/) configuration
|
||||||
├── src * The source of the application
|
├── README.md * This document
|
||||||
│ ├── app * The source code of the application, subdivided by module/page.
|
├── SECURITY.md *
|
||||||
│ ├── assets * Folder for static resources
|
├── server.ts * Angular Universal Node.js Express server
|
||||||
│ │ ├── fonts * Folder for fonts
|
├── tsconfig.app.json * TypeScript config for browser (app)
|
||||||
│ │ ├── i18n * Folder for i18n translations
|
├── tsconfig.json * TypeScript common config
|
||||||
│ | └── en.json5 * i18n translations for English
|
├── tsconfig.server.json * TypeScript config for server
|
||||||
│ │ └── images * Folder for images
|
├── tsconfig.spec.json * TypeScript config for tests
|
||||||
│ ├── backend * Folder containing a mock of the REST API, hosted by the express server
|
├── tsconfig.ts-node.json * TypeScript config for using ts-node directly
|
||||||
│ ├── config *
|
|
||||||
│ ├── index.csr.html * The index file for client side rendering fallback
|
|
||||||
│ ├── index.html * The index file
|
|
||||||
│ ├── main.browser.ts * The bootstrap file for the client
|
|
||||||
│ ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server
|
|
||||||
│ ├── robots.txt * The robots.txt file
|
|
||||||
│ ├── modules *
|
|
||||||
│ ├── styles * Folder containing global styles
|
|
||||||
│ └── themes * Folder containing available themes
|
|
||||||
│ ├── custom * Template folder for creating a custom theme
|
|
||||||
│ └── dspace * Default 'dspace' theme
|
|
||||||
├── tsconfig.json * TypeScript config
|
|
||||||
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
||||||
├── typedoc.json * TYPEDOC configuration
|
├── typedoc.json * TYPEDOC configuration
|
||||||
├── webpack * Webpack (https://webpack.github.io/) config directory
|
|
||||||
│ ├── webpack.browser.ts * Webpack (https://webpack.github.io/) config for client build
|
|
||||||
│ ├── webpack.common.ts *
|
|
||||||
│ ├── webpack.prod.ts * Webpack (https://webpack.github.io/) config for production build
|
|
||||||
│ └── webpack.test.ts * Webpack (https://webpack.github.io/) config for test build
|
|
||||||
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
24
angular.json
24
angular.json
@@ -68,6 +68,12 @@
|
|||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.production.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
"optimization": true,
|
"optimization": true,
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"extractCss": true,
|
"extractCss": true,
|
||||||
@@ -139,6 +145,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"test": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.test.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
@@ -183,7 +199,13 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"optimization": true
|
"optimization": true,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.production.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
2
config/.gitignore
vendored
Normal file
2
config/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
config.*.yml
|
||||||
|
!config.example.yml
|
233
config/config.example.yml
Normal file
233
config/config.example.yml
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# NOTE: will log all redux actions and transfers in console
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
# Angular Universal server settings
|
||||||
|
# NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg.
|
||||||
|
ui:
|
||||||
|
ssl: false
|
||||||
|
host: localhost
|
||||||
|
port: 4000
|
||||||
|
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||||
|
nameSpace: /
|
||||||
|
# The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute).
|
||||||
|
rateLimiter:
|
||||||
|
windowMs: 60000 # 1 minute
|
||||||
|
max: 500 # limit each IP to 500 requests per windowMs
|
||||||
|
|
||||||
|
# The REST API server settings
|
||||||
|
# NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg.
|
||||||
|
rest:
|
||||||
|
ssl: true
|
||||||
|
host: api7.dspace.org
|
||||||
|
port: 443
|
||||||
|
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||||
|
nameSpace: /server
|
||||||
|
|
||||||
|
# Caching settings
|
||||||
|
cache:
|
||||||
|
# NOTE: how long should objects be cached for by default
|
||||||
|
msToLive:
|
||||||
|
default: 900000 # 15 minutes
|
||||||
|
control: max-age=60 # revalidate browser
|
||||||
|
autoSync:
|
||||||
|
defaultTime: 0
|
||||||
|
maxBufferSize: 100
|
||||||
|
timePerMethod:
|
||||||
|
PATCH: 3 # time in seconds
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
auth:
|
||||||
|
# Authentication UI settings
|
||||||
|
ui:
|
||||||
|
# the amount of time before the idle warning is shown
|
||||||
|
timeUntilIdle: 900000 # 15 minutes
|
||||||
|
# the amount of time the user has to react after the idle warning is shown before they are logged out.
|
||||||
|
idleGracePeriod: 300000 # 5 minutes
|
||||||
|
# Authentication REST settings
|
||||||
|
rest:
|
||||||
|
# If the rest token expires in less than this amount of time, it will be refreshed automatically.
|
||||||
|
# This is independent from the idle warning.
|
||||||
|
timeLeftBeforeTokenRefresh: 120000 # 2 minutes
|
||||||
|
|
||||||
|
# Form settings
|
||||||
|
form:
|
||||||
|
# NOTE: Map server-side validators to comparative Angular form validators
|
||||||
|
validatorMap:
|
||||||
|
required: required
|
||||||
|
regex: pattern
|
||||||
|
|
||||||
|
# Notification settings
|
||||||
|
notifications:
|
||||||
|
rtl: false
|
||||||
|
position:
|
||||||
|
- top
|
||||||
|
- right
|
||||||
|
maxStack: 8
|
||||||
|
# NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically
|
||||||
|
timeOut: 5000 # 5 second
|
||||||
|
clickToClose: true
|
||||||
|
# NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale'
|
||||||
|
animate: scale
|
||||||
|
|
||||||
|
# Submission settings
|
||||||
|
submission:
|
||||||
|
autosave:
|
||||||
|
# NOTE: which metadata trigger an autosave
|
||||||
|
metadata: []
|
||||||
|
# NOTE: after how many time (milliseconds) submission is saved automatically
|
||||||
|
# eg. timer: 5 * (1000 * 60); // 5 minutes
|
||||||
|
timer: 0
|
||||||
|
icons:
|
||||||
|
metadata:
|
||||||
|
# NOTE: example of configuration
|
||||||
|
# # NOTE: metadata name
|
||||||
|
# - name: dc.author
|
||||||
|
# # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used
|
||||||
|
# style: fas fa-user
|
||||||
|
- name: dc.author
|
||||||
|
style: fas fa-user
|
||||||
|
# default configuration
|
||||||
|
- name: default
|
||||||
|
style: ''
|
||||||
|
authority:
|
||||||
|
confidence:
|
||||||
|
# NOTE: example of configuration
|
||||||
|
# # NOTE: confidence value
|
||||||
|
# - name: dc.author
|
||||||
|
# # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used
|
||||||
|
# style: fa-user
|
||||||
|
- value: 600
|
||||||
|
style: text-success
|
||||||
|
- value: 500
|
||||||
|
style: text-info
|
||||||
|
- value: 400
|
||||||
|
style: text-warning
|
||||||
|
# default configuration
|
||||||
|
- value: default
|
||||||
|
style: text-muted
|
||||||
|
|
||||||
|
# Default Language in which the UI will be rendered if the user's browser language is not an active language
|
||||||
|
defaultLanguage: en
|
||||||
|
|
||||||
|
# Languages. DSpace Angular holds a message catalog for each of the following languages.
|
||||||
|
# When set to active, users will be able to switch to the use of this language in the user interface.
|
||||||
|
languages:
|
||||||
|
- code: en
|
||||||
|
label: English
|
||||||
|
active: true
|
||||||
|
- code: cs
|
||||||
|
label: Čeština
|
||||||
|
active: true
|
||||||
|
- code: de
|
||||||
|
label: Deutsch
|
||||||
|
active: true
|
||||||
|
- code: es
|
||||||
|
label: Español
|
||||||
|
active: true
|
||||||
|
- code: fr
|
||||||
|
label: Français
|
||||||
|
active: true
|
||||||
|
- code: lv
|
||||||
|
label: Latviešu
|
||||||
|
active: true
|
||||||
|
- code: hu
|
||||||
|
label: Magyar
|
||||||
|
active: true
|
||||||
|
- code: nl
|
||||||
|
label: Nederlands
|
||||||
|
active: true
|
||||||
|
- code: pt-PT
|
||||||
|
label: Português
|
||||||
|
active: true
|
||||||
|
- code: pt-BR
|
||||||
|
label: Português do Brasil
|
||||||
|
active: true
|
||||||
|
- code: fi
|
||||||
|
label: Suomi
|
||||||
|
active: true
|
||||||
|
|
||||||
|
# Browse-By Pages
|
||||||
|
browseBy:
|
||||||
|
# Amount of years to display using jumps of one year (current year - oneYearLimit)
|
||||||
|
oneYearLimit: 10
|
||||||
|
# Limit for years to display using jumps of five years (current year - fiveYearLimit)
|
||||||
|
fiveYearLimit: 30
|
||||||
|
# The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
||||||
|
defaultLowerLimit: 1900
|
||||||
|
|
||||||
|
# Item Page Config
|
||||||
|
item:
|
||||||
|
edit:
|
||||||
|
undoTimeout: 10000 # 10 seconds
|
||||||
|
|
||||||
|
# Collection Page Config
|
||||||
|
collection:
|
||||||
|
edit:
|
||||||
|
undoTimeout: 10000 # 10 seconds
|
||||||
|
|
||||||
|
# Theme Config
|
||||||
|
themes:
|
||||||
|
# Add additional themes here. In the case where multiple themes match a route, the first one
|
||||||
|
# in this list will get priority. It is advisable to always have a theme that matches
|
||||||
|
# every route as the last one
|
||||||
|
#
|
||||||
|
# # A theme with a handle property will match the community, collection or item with the given
|
||||||
|
# # handle, and all collections and/or items within it
|
||||||
|
# - name: 'custom',
|
||||||
|
# handle: '10673/1233'
|
||||||
|
#
|
||||||
|
# # A theme with a regex property will match the route using a regular expression. If it
|
||||||
|
# # matches the route for a community or collection it will also apply to all collections
|
||||||
|
# # and/or items within it
|
||||||
|
# - name: 'custom',
|
||||||
|
# regex: 'collections\/e8043bc2.*'
|
||||||
|
#
|
||||||
|
# # A theme with a uuid property will match the community, collection or item with the given
|
||||||
|
# # ID, and all collections and/or items within it
|
||||||
|
# - name: 'custom',
|
||||||
|
# uuid: '0958c910-2037-42a9-81c7-dca80e3892b4'
|
||||||
|
#
|
||||||
|
# # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found
|
||||||
|
# # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default.
|
||||||
|
# - name: 'custom-A',
|
||||||
|
# extends: 'custom-B',
|
||||||
|
# # Any of the matching properties above can be used
|
||||||
|
# handle: '10673/34'
|
||||||
|
#
|
||||||
|
# - name: 'custom-B',
|
||||||
|
# extends: 'custom',
|
||||||
|
# handle: '10673/12'
|
||||||
|
#
|
||||||
|
# # A theme with only a name will match every route
|
||||||
|
# name: 'custom'
|
||||||
|
#
|
||||||
|
# # This theme will use the default bootstrap styling for DSpace components
|
||||||
|
# - name: BASE_THEME_NAME
|
||||||
|
#
|
||||||
|
- name: dspace
|
||||||
|
headTags:
|
||||||
|
- tagName: link
|
||||||
|
attributes:
|
||||||
|
rel: icon
|
||||||
|
href: assets/dspace/images/favicons/favicon.ico
|
||||||
|
sizes: any
|
||||||
|
- tagName: link
|
||||||
|
attributes:
|
||||||
|
rel: icon
|
||||||
|
href: assets/dspace/images/favicons/favicon.svg
|
||||||
|
type: image/svg+xml
|
||||||
|
- tagName: link
|
||||||
|
attributes:
|
||||||
|
rel: apple-touch-icon
|
||||||
|
href: assets/dspace/images/favicons/apple-touch-icon.png
|
||||||
|
- tagName: link
|
||||||
|
attributes:
|
||||||
|
rel: manifest
|
||||||
|
href: assets/dspace/images/favicons/manifest.webmanifest
|
||||||
|
|
||||||
|
# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video').
|
||||||
|
# For images, this enables a gallery viewer where you can zoom or page through images.
|
||||||
|
# For videos, this enables embedded video streaming
|
||||||
|
mediaViewer:
|
||||||
|
image: false
|
||||||
|
video: false
|
5
config/config.yml
Normal file
5
config/config.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
rest:
|
||||||
|
ssl: true
|
||||||
|
host: api7.dspace.org
|
||||||
|
port: 443
|
||||||
|
nameSpace: /server
|
2
cypress/.gitignore
vendored
Normal file
2
cypress/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
screenshots/
|
||||||
|
videos/
|
@@ -53,7 +53,7 @@ describe('Search Page', () => {
|
|||||||
|
|
||||||
// Click to display grid view
|
// Click to display grid view
|
||||||
// TODO: These buttons should likely have an easier way to uniquely select
|
// TODO: These buttons should likely have an easier way to uniquely select
|
||||||
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?spc.sf=score&spc.sd=DESC&view=grid"] > .fas').click();
|
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?view=grid"] > .fas').click();
|
||||||
|
|
||||||
// <ds-search-page> tag must be loaded
|
// <ds-search-page> tag must be loaded
|
||||||
cy.get('ds-search-page').should('exist');
|
cy.get('ds-search-page').should('exist');
|
||||||
|
@@ -29,10 +29,6 @@ docker push dspace/dspace-angular:dspace-7_x
|
|||||||
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
|
||||||
- cli.assetstore.yml
|
- cli.assetstore.yml
|
||||||
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
|
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
|
||||||
- environment.dev.ts
|
|
||||||
- Environment file for running DSpace Angular in Docker
|
|
||||||
- local.cfg
|
|
||||||
- Environment file for running the DSpace 7 REST API in Docker.
|
|
||||||
|
|
||||||
|
|
||||||
## To refresh / pull DSpace images from Dockerhub
|
## To refresh / pull DSpace images from Dockerhub
|
||||||
|
@@ -18,10 +18,19 @@ services:
|
|||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
||||||
container_name: dspace-cli
|
container_name: dspace-cli
|
||||||
#environment:
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
volumes:
|
volumes:
|
||||||
- "assetstore:/dspace/assetstore"
|
- "assetstore:/dspace/assetstore"
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
entrypoint: /dspace/bin/dspace
|
entrypoint: /dspace/bin/dspace
|
||||||
command: help
|
command: help
|
||||||
networks:
|
networks:
|
||||||
|
@@ -17,6 +17,19 @@ services:
|
|||||||
# DSpace (backend) webapp container
|
# DSpace (backend) webapp container
|
||||||
dspace:
|
dspace:
|
||||||
container_name: dspace
|
container_name: dspace
|
||||||
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir, dspace.server.url and dspace.ui.url
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
dspace__P__server__P__url: http://localhost:8080/server
|
||||||
|
dspace__P__ui__P__url: http://localhost:4000
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:dspace-7_x-test
|
image: dspace/dspace:dspace-7_x-test
|
||||||
@@ -29,7 +42,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||||
- solr_configs:/dspace/solr
|
- solr_configs:/dspace/solr
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
|
@@ -13,10 +13,32 @@
|
|||||||
version: '3.7'
|
version: '3.7'
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
dspacenet:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
# Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container.
|
||||||
|
# If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below.
|
||||||
|
- subnet: 172.23.0.0/16
|
||||||
services:
|
services:
|
||||||
# DSpace (backend) webapp container
|
# DSpace (backend) webapp container
|
||||||
dspace:
|
dspace:
|
||||||
container_name: dspace
|
container_name: dspace
|
||||||
|
environment:
|
||||||
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||||
|
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||||
|
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||||
|
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
|
||||||
|
dspace__P__dir: /dspace
|
||||||
|
dspace__P__server__P__url: http://localhost:8080/server
|
||||||
|
dspace__P__ui__P__url: http://localhost:4000
|
||||||
|
dspace__P__name: 'DSpace Started with Docker Compose'
|
||||||
|
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||||
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
|
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||||
|
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||||
|
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||||
image: dspace/dspace:dspace-7_x-test
|
image: dspace/dspace:dspace-7_x-test
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
@@ -29,7 +51,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
- "./local.cfg:/dspace/config/local.cfg"
|
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
||||||
- solr_configs:/dspace/solr
|
- solr_configs:/dspace/solr
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
|
@@ -16,10 +16,14 @@ services:
|
|||||||
dspace-angular:
|
dspace-angular:
|
||||||
container_name: dspace-angular
|
container_name: dspace-angular
|
||||||
environment:
|
environment:
|
||||||
DSPACE_HOST: dspace-angular
|
DSPACE_UI_SSL: 'false'
|
||||||
DSPACE_NAMESPACE: /
|
DSPACE_UI_HOST: dspace-angular
|
||||||
DSPACE_PORT: '4000'
|
DSPACE_UI_PORT: '4000'
|
||||||
DSPACE_SSL: "false"
|
DSPACE_UI_NAMESPACE: /
|
||||||
|
DSPACE_REST_SSL: 'false'
|
||||||
|
DSPACE_REST_HOST: localhost
|
||||||
|
DSPACE_REST_PORT: 8080
|
||||||
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: dspace/dspace-angular:dspace-7_x
|
image: dspace/dspace-angular:dspace-7_x
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
@@ -33,5 +37,3 @@ services:
|
|||||||
target: 9876
|
target: 9876
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
|
||||||
- ./environment.dev.ts:/app/src/environments/environment.dev.ts
|
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* The contents of this file are subject to the license and copyright
|
|
||||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
|
||||||
* tree and available online at
|
|
||||||
*
|
|
||||||
* http://www.dspace.org/license/
|
|
||||||
*/
|
|
||||||
// This file is based on environment.template.ts provided by Angular UI
|
|
||||||
export const environment = {
|
|
||||||
// Default to using the local REST API (running in Docker)
|
|
||||||
rest: {
|
|
||||||
ssl: false,
|
|
||||||
host: 'localhost',
|
|
||||||
port: 8080,
|
|
||||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
|
||||||
nameSpace: '/server'
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,6 +0,0 @@
|
|||||||
dspace.dir=/dspace
|
|
||||||
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
|
||||||
dspace.server.url=http://localhost:8080/server
|
|
||||||
dspace.ui.url=http://localhost:4000
|
|
||||||
dspace.name=DSpace Started with Docker Compose
|
|
||||||
solr.server=http://dspacesolr:8983/solr
|
|
@@ -1,26 +1,30 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
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.
|
Default configuration file is located at `config/config.yml`. All configuration options should be listed in the default typescript file `src/config/default-app-config.ts`. Please do not change this file directly! To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point.
|
||||||
|
|
||||||
- Create a new `environment.dev.ts` file in `src/environments/` for `development` environment;
|
- Create a new `config.(dev or development).yml` file in `config/` for `development` environment;
|
||||||
- Create a new `environment.prod.ts` file in `src/environments/` for `production` environment;
|
- Create a new `config.(prod or production).yml` file in `config/` for `production` environment;
|
||||||
|
|
||||||
Some few configuration options can be overridden by setting environment variables. These and the variable names are listed below.
|
Alternatively, create a desired app config file at an external location and set the path as environment variable `DSPACE_APP_CONFIG_PATH`.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
```
|
||||||
|
DSPACE_APP_CONFIG_PATH=/usr/local/dspace/config/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration options can be overridden by setting environment variables.
|
||||||
|
|
||||||
## Nodejs server
|
## Nodejs server
|
||||||
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
||||||
|
|
||||||
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):
|
||||||
```
|
|
||||||
export const environment = {
|
```yaml
|
||||||
// Angular UI settings.
|
ui:
|
||||||
ui: {
|
ssl: false
|
||||||
ssl: false,
|
host: localhost
|
||||||
host: 'localhost',
|
port: 4000
|
||||||
port: 4000,
|
nameSpace: /
|
||||||
nameSpace: '/'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
||||||
@@ -30,21 +34,24 @@ Alternately you can set the following environment variables. If any of these are
|
|||||||
DSPACE_PORT=4000
|
DSPACE_PORT=4000
|
||||||
DSPACE_NAMESPACE=/
|
DSPACE_NAMESPACE=/
|
||||||
```
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
DSPACE_UI_SSL=true
|
||||||
|
DSPACE_UI_HOST=localhost
|
||||||
|
DSPACE_UI_PORT=4000
|
||||||
|
DSPACE_UI_NAMESPACE=/
|
||||||
|
```
|
||||||
|
|
||||||
## DSpace's REST endpoint
|
## DSpace's REST endpoint
|
||||||
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:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
export const environment = {
|
rest:
|
||||||
// The REST API server settings.
|
ssl: true
|
||||||
rest: {
|
host: api7.dspace.org
|
||||||
ssl: true,
|
port: 443
|
||||||
host: 'api7.dspace.org',
|
nameSpace: /server
|
||||||
port: 443,
|
|
||||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
|
||||||
nameSpace: '/server'
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
||||||
@@ -55,6 +62,21 @@ Alternately you can set the following environment variables. If any of these are
|
|||||||
DSPACE_REST_NAMESPACE=/server
|
DSPACE_REST_NAMESPACE=/server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Environment variable naming convention
|
||||||
|
|
||||||
|
Settings can be set using the following convention for naming the environment variables:
|
||||||
|
|
||||||
|
1. replace all `.` with `_`
|
||||||
|
2. convert all characters to upper case
|
||||||
|
3. prefix with `DSPACE_`
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
cache.msToLive.default => DSPACE_CACHE_MSTOLIVE_DEFAULT
|
||||||
|
auth.ui.timeUntilIdle => DSPACE_AUTH_UI_TIMEUNTILIDLE
|
||||||
|
```
|
||||||
|
|
||||||
## Supporting analytics services other than Google Analytics
|
## Supporting analytics services other than Google Analytics
|
||||||
This project makes use of [Angulartics](https://angulartics.github.io/angulartics2/) to track usage events and send them to Google Analytics.
|
This project makes use of [Angulartics](https://angulartics.github.io/angulartics2/) to track usage events and send them to Google Analytics.
|
||||||
|
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"watch": ["src/environments/mock-environment.ts"],
|
|
||||||
"ext": "ts",
|
|
||||||
"exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts"
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"watch": ["src/environments"],
|
"watch": [
|
||||||
"ext": "ts",
|
"config"
|
||||||
"ignore": ["src/environments/environment.ts", "src/environments/mock-environment.ts"],
|
],
|
||||||
"exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev"
|
"ext": "json"
|
||||||
}
|
}
|
||||||
|
53
package.json
53
package.json
@@ -3,53 +3,40 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"config:dev": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev",
|
"config:watch": "nodemon",
|
||||||
"config:prod": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --prod",
|
"test:rest": "ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
|
||||||
"config:test": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts",
|
|
||||||
"config:test:watch": "nodemon --config mock-nodemon.json",
|
|
||||||
"config:dev:watch": "nodemon",
|
|
||||||
"config:check:rest": "yarn run config:prod && ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
|
|
||||||
"config:dev:check:rest": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
|
|
||||||
"prestart:dev": "yarn run config:dev",
|
|
||||||
"prebuild": "yarn run config:dev",
|
|
||||||
"pretest": "yarn run config:test",
|
|
||||||
"pretest:watch": "yarn run config:test",
|
|
||||||
"pretest:headless": "yarn run config:test",
|
|
||||||
"prebuild:prod": "yarn run config:prod",
|
|
||||||
"pree2e": "yarn run config:prod",
|
|
||||||
"start": "yarn run start:prod",
|
"start": "yarn run start:prod",
|
||||||
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
"start:dev": "nodemon --exec \"cross-env NODE_ENV=development yarn run serve\"",
|
||||||
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
"start:prod": "yarn run build:prod && cross-env NODE_ENV=production yarn run serve:ssr",
|
||||||
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
|
||||||
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
||||||
|
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||||
|
"serve:ssr": "node dist/server/main",
|
||||||
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build:stats": "ng build --stats-json",
|
"build:stats": "ng build --stats-json",
|
||||||
"build:prod": "yarn run build:ssr",
|
"build:prod": "yarn run build:ssr",
|
||||||
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
|
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
|
||||||
"test:watch": "npm-run-all --parallel config:test:watch test",
|
"test": "ng test --sourceMap=true --watch=false --configuration test",
|
||||||
"test": "ng test --sourceMap=true --watch=true",
|
"test:watch": "nodemon --exec \"ng test --sourceMap=true --watch=true --configuration test\"",
|
||||||
"test:headless": "ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage",
|
"test:headless": "ng test --sourceMap=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"lint-fix": "ng lint --fix=true",
|
"lint-fix": "ng lint --fix=true",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"serve:ssr": "node dist/server/main",
|
"clean:dev:config": "rimraf src/assets/config.json",
|
||||||
"clean:coverage": "rimraf coverage",
|
"clean:coverage": "rimraf coverage",
|
||||||
"clean:dist": "rimraf dist",
|
"clean:dist": "rimraf dist",
|
||||||
"clean:doc": "rimraf doc",
|
"clean:doc": "rimraf doc",
|
||||||
"clean:log": "rimraf *.log*",
|
"clean:log": "rimraf *.log*",
|
||||||
"clean:json": "rimraf *.records.json",
|
"clean:json": "rimraf *.records.json",
|
||||||
"clean:bld": "rimraf build",
|
|
||||||
"clean:node": "rimraf node_modules",
|
"clean:node": "rimraf node_modules",
|
||||||
"clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld",
|
"clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json",
|
||||||
"clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node",
|
"clean": "yarn run clean:prod && yarn run clean:dev:config && yarn run clean:node",
|
||||||
"clean:env": "rimraf src/environments/environment.ts",
|
"sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
||||||
"sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
|
||||||
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
||||||
"merge-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
"merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
||||||
"postinstall": "ngcc",
|
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:run": "cypress run"
|
"cypress:run": "cypress run",
|
||||||
|
"env:yaml": "ts-node --project ./tsconfig.ts-node.json scripts/env-to-yaml.ts"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"fs": false,
|
"fs": false,
|
||||||
@@ -74,7 +61,6 @@
|
|||||||
"@angular/platform-browser-dynamic": "~11.2.14",
|
"@angular/platform-browser-dynamic": "~11.2.14",
|
||||||
"@angular/platform-server": "~11.2.14",
|
"@angular/platform-server": "~11.2.14",
|
||||||
"@angular/router": "~11.2.14",
|
"@angular/router": "~11.2.14",
|
||||||
"@angularclass/bootloader": "1.0.1",
|
|
||||||
"@kolkov/ngx-gallery": "^1.2.3",
|
"@kolkov/ngx-gallery": "^1.2.3",
|
||||||
"@ng-bootstrap/ng-bootstrap": "9.1.3",
|
"@ng-bootstrap/ng-bootstrap": "9.1.3",
|
||||||
"@ng-dynamic-forms/core": "^13.0.0",
|
"@ng-dynamic-forms/core": "^13.0.0",
|
||||||
@@ -102,9 +88,10 @@
|
|||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"https": "1.0.0",
|
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"http-proxy-middleware": "^1.0.5",
|
||||||
|
"https": "1.0.0",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"json5": "^2.1.3",
|
"json5": "^2.1.3",
|
||||||
"jsonschema": "1.4.0",
|
"jsonschema": "1.4.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
@@ -157,6 +144,7 @@
|
|||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"compression-webpack-plugin": "^3.0.1",
|
"compression-webpack-plugin": "^3.0.1",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "3.4.0",
|
"css-loader": "3.4.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"cypress": "8.6.0",
|
"cypress": "8.6.0",
|
||||||
@@ -176,8 +164,7 @@
|
|||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.15",
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||||
"postcss-apply": "0.11.0",
|
"postcss-apply": "0.11.0",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
|
39
scripts/env-to-yaml.ts
Normal file
39
scripts/env-to-yaml.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script to help convert previous version environment.*.ts to yaml.
|
||||||
|
*
|
||||||
|
* Usage (see package.json):
|
||||||
|
*
|
||||||
|
* yarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file) *
|
||||||
|
*/
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args[0] === undefined) {
|
||||||
|
console.log(`Usage:\n\tyarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file)\n`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const envFullPath = join(process.cwd(), args[0]);
|
||||||
|
|
||||||
|
if (!fs.existsSync(envFullPath)) {
|
||||||
|
console.error(`Error:\n${envFullPath} does not exist\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const env = require(envFullPath);
|
||||||
|
|
||||||
|
const config = yaml.dump(env);
|
||||||
|
if (args[1]) {
|
||||||
|
const ymlFullPath = join(process.cwd(), args[1]);
|
||||||
|
fs.writeFileSync(ymlFullPath, config);
|
||||||
|
} else {
|
||||||
|
console.log(config);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
@@ -1,11 +1,14 @@
|
|||||||
import { environment } from '../src/environments/environment';
|
|
||||||
|
|
||||||
import * as child from 'child_process';
|
import * as child from 'child_process';
|
||||||
|
|
||||||
|
import { AppConfig } from '../src/config/app-config.interface';
|
||||||
|
import { buildAppConfig } from '../src/config/config.server';
|
||||||
|
|
||||||
|
const appConfig: AppConfig = buildAppConfig();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls `ng serve` with the following arguments configured for the UI in the environment file: host, port, nameSpace, ssl
|
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
||||||
*/
|
*/
|
||||||
child.spawn(
|
child.spawn(
|
||||||
`ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`,
|
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`,
|
||||||
{ stdio: 'inherit', shell: true }
|
{ stdio: 'inherit', shell: true }
|
||||||
);
|
);
|
||||||
|
@@ -1,116 +0,0 @@
|
|||||||
import { writeFile } from 'fs';
|
|
||||||
import { environment as commonEnv } from '../src/environments/environment.common';
|
|
||||||
import { GlobalConfig } from '../src/config/global-config.interface';
|
|
||||||
import { ServerConfig } from '../src/config/server-config.interface';
|
|
||||||
import { hasValue } from '../src/app/shared/empty.util';
|
|
||||||
|
|
||||||
// Configure Angular `environment.ts` file path
|
|
||||||
const targetPath = './src/environments/environment.ts';
|
|
||||||
// Load node modules
|
|
||||||
const colors = require('colors');
|
|
||||||
require('dotenv').config();
|
|
||||||
const merge = require('deepmerge');
|
|
||||||
const mergeOptions = { arrayMerge: (destinationArray, sourceArray, options) => sourceArray };
|
|
||||||
const environment = process.argv[2];
|
|
||||||
let environmentFilePath;
|
|
||||||
let production = false;
|
|
||||||
|
|
||||||
switch (environment) {
|
|
||||||
case '--prod':
|
|
||||||
case '--production':
|
|
||||||
production = true;
|
|
||||||
console.log(`Building ${colors.red.bold(`production`)} environment`);
|
|
||||||
environmentFilePath = '../src/environments/environment.prod.ts';
|
|
||||||
break;
|
|
||||||
case '--test':
|
|
||||||
console.log(`Building ${colors.blue.bold(`test`)} environment`);
|
|
||||||
environmentFilePath = '../src/environments/environment.test.ts';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(`Building ${colors.green.bold(`development`)} environment`);
|
|
||||||
environmentFilePath = '../src/environments/environment.dev.ts';
|
|
||||||
}
|
|
||||||
|
|
||||||
const processEnv = {
|
|
||||||
ui: createServerConfig(
|
|
||||||
process.env.DSPACE_HOST,
|
|
||||||
process.env.DSPACE_PORT,
|
|
||||||
process.env.DSPACE_NAMESPACE,
|
|
||||||
process.env.DSPACE_SSL),
|
|
||||||
rest: createServerConfig(
|
|
||||||
process.env.DSPACE_REST_HOST,
|
|
||||||
process.env.DSPACE_REST_PORT,
|
|
||||||
process.env.DSPACE_REST_NAMESPACE,
|
|
||||||
process.env.DSPACE_REST_SSL)
|
|
||||||
} as GlobalConfig;
|
|
||||||
|
|
||||||
import(environmentFilePath)
|
|
||||||
.then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv], mergeOptions)))
|
|
||||||
.catch(() => {
|
|
||||||
console.log(colors.yellow.bold(`No specific environment file found for ` + environment));
|
|
||||||
generateEnvironmentFile(merge(commonEnv, processEnv, mergeOptions))
|
|
||||||
});
|
|
||||||
|
|
||||||
function generateEnvironmentFile(file: GlobalConfig): void {
|
|
||||||
file.production = production;
|
|
||||||
buildBaseUrls(file);
|
|
||||||
const contents = `export const environment = ` + JSON.stringify(file);
|
|
||||||
writeFile(targetPath, contents, (err) => {
|
|
||||||
if (err) {
|
|
||||||
throw console.error(err);
|
|
||||||
} else {
|
|
||||||
console.log(`Angular ${colors.bold('environment.ts')} file generated correctly at ${colors.bold(targetPath)} \n`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow to override a few important options by environment variables
|
|
||||||
function createServerConfig(host?: string, port?: string, nameSpace?: string, ssl?: string): ServerConfig {
|
|
||||||
const result = {} as any;
|
|
||||||
if (hasValue(host)) {
|
|
||||||
result.host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValue(nameSpace)) {
|
|
||||||
result.nameSpace = nameSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValue(port)) {
|
|
||||||
result.port = Number(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValue(ssl)) {
|
|
||||||
result.ssl = ssl.trim().match(/^(true|1|yes)$/i) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildBaseUrls(config: GlobalConfig): void {
|
|
||||||
for (const key in config) {
|
|
||||||
if (config.hasOwnProperty(key) && config[key].host) {
|
|
||||||
config[key].baseUrl = [
|
|
||||||
getProtocol(config[key].ssl),
|
|
||||||
getHost(config[key].host),
|
|
||||||
getPort(config[key].port),
|
|
||||||
getNameSpace(config[key].nameSpace)
|
|
||||||
].join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProtocol(ssl: boolean): string {
|
|
||||||
return ssl ? 'https://' : 'http://';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHost(host: string): string {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPort(port: number): string {
|
|
||||||
return port ? (port !== 80 && port !== 443) ? ':' + port : '' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNameSpace(nameSpace: string): string {
|
|
||||||
return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : '';
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
import { copyFile } from 'fs';
|
|
||||||
|
|
||||||
// Configure Angular `environment.ts` file path
|
|
||||||
const sourcePath = './src/environments/mock-environment.ts';
|
|
||||||
const targetPath = './src/environments/environment.ts';
|
|
||||||
|
|
||||||
// destination.txt will be created or overwritten by default.
|
|
||||||
copyFile(sourcePath, targetPath, (err) => {
|
|
||||||
if (err) throw err;
|
|
||||||
console.log(sourcePath + ' was copied to ' + targetPath);
|
|
||||||
});
|
|
@@ -1,21 +1,25 @@
|
|||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import { environment } from '../src/environments/environment';
|
|
||||||
|
import { AppConfig } from '../src/config/app-config.interface';
|
||||||
|
import { buildAppConfig } from '../src/config/config.server';
|
||||||
|
|
||||||
|
const appConfig: AppConfig = buildAppConfig();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Script to test the connection with the configured REST API (in the 'rest' settings of your environment.*.ts)
|
* Script to test the connection with the configured REST API (in the 'rest' settings of your config.*.yaml)
|
||||||
*
|
*
|
||||||
* This script is useful to test for any Node.js connection issues with your REST API.
|
* This script is useful to test for any Node.js connection issues with your REST API.
|
||||||
*
|
*
|
||||||
* Usage (see package.json): yarn test:rest-api
|
* Usage (see package.json): yarn test:rest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Get root URL of configured REST API
|
// Get root URL of configured REST API
|
||||||
const restUrl = environment.rest.baseUrl + '/api';
|
const restUrl = appConfig.rest.baseUrl + '/api';
|
||||||
console.log(`...Testing connection to REST API at ${restUrl}...\n`);
|
console.log(`...Testing connection to REST API at ${restUrl}...\n`);
|
||||||
|
|
||||||
// If SSL enabled, test via HTTPS, else via HTTP
|
// If SSL enabled, test via HTTPS, else via HTTP
|
||||||
if (environment.rest.ssl) {
|
if (appConfig.rest.ssl) {
|
||||||
const req = https.request(restUrl, (res) => {
|
const req = https.request(restUrl, (res) => {
|
||||||
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
||||||
res.on('data', (data) => {
|
res.on('data', (data) => {
|
||||||
@@ -55,7 +59,7 @@ function checkJSONResponse(responseData: any): any {
|
|||||||
console.log(`\t"dspaceVersion" = ${parsedData.dspaceVersion}`);
|
console.log(`\t"dspaceVersion" = ${parsedData.dspaceVersion}`);
|
||||||
console.log(`\t"dspaceUI" = ${parsedData.dspaceUI}`);
|
console.log(`\t"dspaceUI" = ${parsedData.dspaceUI}`);
|
||||||
console.log(`\t"dspaceServer" = ${parsedData.dspaceServer}`);
|
console.log(`\t"dspaceServer" = ${parsedData.dspaceServer}`);
|
||||||
console.log(`\t"dspaceServer" property matches UI's "rest" config? ${(parsedData.dspaceServer === environment.rest.baseUrl)}`);
|
console.log(`\t"dspaceServer" property matches UI's "rest" config? ${(parsedData.dspaceServer === appConfig.rest.baseUrl)}`);
|
||||||
// Check for "authn" and "sites" in "_links" section as they should always exist (even if no data)!
|
// Check for "authn" and "sites" in "_links" section as they should always exist (even if no data)!
|
||||||
const linksFound: string[] = Object.keys(parsedData._links);
|
const linksFound: string[] = Object.keys(parsedData._links);
|
||||||
console.log(`\tDoes "/api" endpoint have HAL links ("_links" section)? ${linksFound.includes('authn') && linksFound.includes('sites')}`);
|
console.log(`\tDoes "/api" endpoint have HAL links ("_links" section)? ${linksFound.includes('authn') && linksFound.includes('sites')}`);
|
||||||
|
28
server.ts
28
server.ts
@@ -19,27 +19,34 @@ import 'zone.js/dist/zone-node';
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import 'rxjs';
|
import 'rxjs';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as pem from 'pem';
|
import * as pem from 'pem';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import * as morgan from 'morgan';
|
import * as morgan from 'morgan';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as bodyParser from 'body-parser';
|
import * as bodyParser from 'body-parser';
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
|
|
||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||||
|
|
||||||
import { environment } from './src/environments/environment';
|
import { environment } from './src/environments/environment';
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
import { hasValue, hasNoValue } from './src/app/shared/empty.util';
|
import { hasValue, hasNoValue } from './src/app/shared/empty.util';
|
||||||
import { APP_BASE_HREF } from '@angular/common';
|
|
||||||
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
||||||
|
|
||||||
import { ServerAppModule } from './src/main.server';
|
import { ServerAppModule } from './src/main.server';
|
||||||
|
|
||||||
|
import { buildAppConfig } from './src/config/config.server';
|
||||||
|
import { AppConfig, APP_CONFIG } from './src/config/app-config.interface';
|
||||||
|
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set path for the browser application's dist folder
|
* Set path for the browser application's dist folder
|
||||||
*/
|
*/
|
||||||
@@ -51,6 +58,11 @@ const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : '
|
|||||||
|
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
|
||||||
|
const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/config.json'));
|
||||||
|
|
||||||
|
// extend environment with app config for server
|
||||||
|
extendEnvironmentWithAppConfig(environment, appConfig);
|
||||||
|
|
||||||
// The Express app is exported so that it can be used by serverless Functions.
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
export function app() {
|
export function app() {
|
||||||
|
|
||||||
@@ -100,7 +112,11 @@ export function app() {
|
|||||||
provide: RESPONSE,
|
provide: RESPONSE,
|
||||||
useValue: (options as any).req.res,
|
useValue: (options as any).req.res,
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
|
provide: APP_CONFIG,
|
||||||
|
useValue: environment
|
||||||
|
}
|
||||||
|
]
|
||||||
})(_, (options as any), callback)
|
})(_, (options as any), callback)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -237,14 +253,14 @@ function start() {
|
|||||||
if (environment.ui.ssl) {
|
if (environment.ui.ssl) {
|
||||||
let serviceKey;
|
let serviceKey;
|
||||||
try {
|
try {
|
||||||
serviceKey = fs.readFileSync('./config/ssl/key.pem');
|
serviceKey = readFileSync('./config/ssl/key.pem');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Service key not found at ./config/ssl/key.pem');
|
console.warn('Service key not found at ./config/ssl/key.pem');
|
||||||
}
|
}
|
||||||
|
|
||||||
let certificate;
|
let certificate;
|
||||||
try {
|
try {
|
||||||
certificate = fs.readFileSync('./config/ssl/cert.pem');
|
certificate = readFileSync('./config/ssl/cert.pem');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Certificate not found at ./config/ssl/key.pem');
|
console.warn('Certificate not found at ./config/ssl/key.pem');
|
||||||
}
|
}
|
||||||
|
@@ -9,13 +9,15 @@ import { GroupFormComponent } from './group-registry/group-form/group-form.compo
|
|||||||
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
||||||
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
||||||
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
||||||
|
import { FormModule } from '../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
AccessControlRoutingModule
|
AccessControlRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EPeopleRegistryComponent,
|
EPeopleRegistryComponent,
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div between class="btn-group">
|
<div between class="btn-group">
|
||||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)">
|
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
|
||||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,9 +36,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
|
<ds-loading [showMessage]="false" *ngIf="!formGroup"></ds-loading>
|
||||||
|
|
||||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||||
|
|
||||||
|
<ds-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-loading>
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
|
@@ -2,7 +2,7 @@ import { Observable, of as observableOf } from 'rxjs';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -14,6 +14,7 @@ import { EPerson } from '../../../core/eperson/models/eperson.model';
|
|||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { EPeopleRegistryComponent } from '../epeople-registry.component';
|
||||||
import { EPersonFormComponent } from './eperson-form.component';
|
import { EPersonFormComponent } from './eperson-form.component';
|
||||||
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
|
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
@@ -28,9 +29,8 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
|
|||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
|
|
||||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||||
|
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||||
|
|
||||||
describe('EPersonFormComponent', () => {
|
describe('EPersonFormComponent', () => {
|
||||||
let component: EPersonFormComponent;
|
let component: EPersonFormComponent;
|
||||||
@@ -42,6 +42,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
let authService: AuthServiceStub;
|
let authService: AuthServiceStub;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let groupsDataService: GroupDataService;
|
let groupsDataService: GroupDataService;
|
||||||
|
let epersonRegistrationService: EpersonRegistrationService;
|
||||||
|
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
@@ -199,12 +200,18 @@ describe('EPersonFormComponent', () => {
|
|||||||
{ provide: AuthService, useValue: authService },
|
{ provide: AuthService, useValue: authService },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring']) }
|
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])},
|
||||||
|
{ provide: EpersonRegistrationService, useValue: epersonRegistrationService },
|
||||||
|
EPeopleRegistryComponent
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
||||||
|
registerEmail: createSuccessfulRemoteDataObject$(null)
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(EPersonFormComponent);
|
fixture = TestBed.createComponent(EPersonFormComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
@@ -514,4 +521,23 @@ describe('EPersonFormComponent', () => {
|
|||||||
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);
|
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Reset Password', () => {
|
||||||
|
let ePersonId;
|
||||||
|
let ePersonEmail;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ePersonId = 'testEPersonId';
|
||||||
|
ePersonEmail = 'person.email@4science.it';
|
||||||
|
component.epersonInitial = Object.assign(new EPerson(), {
|
||||||
|
id: ePersonId,
|
||||||
|
email: ePersonEmail
|
||||||
|
});
|
||||||
|
component.resetPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call epersonRegistrationService.registerEmail', () => {
|
||||||
|
expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -34,6 +34,8 @@ import { NoContent } from '../../../core/shared/NoContent.model';
|
|||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||||
|
import { Registration } from '../../../core/shared/registration.model';
|
||||||
|
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-eperson-form',
|
selector: 'ds-eperson-form',
|
||||||
@@ -121,7 +123,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* Observable whether or not the admin is allowed to reset the EPerson's password
|
* Observable whether or not the admin is allowed to reset the EPerson's password
|
||||||
* TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
|
* TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
|
||||||
*/
|
*/
|
||||||
canReset$: Observable<boolean> = observableOf(false);
|
canReset$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable whether or not the admin is allowed to delete the EPerson
|
* Observable whether or not the admin is allowed to delete the EPerson
|
||||||
@@ -167,7 +169,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
emailValueChangeSubscribe: Subscription;
|
emailValueChangeSubscribe: Subscription;
|
||||||
|
|
||||||
constructor(protected changeDetectorRef: ChangeDetectorRef,
|
constructor(
|
||||||
|
protected changeDetectorRef: ChangeDetectorRef,
|
||||||
public epersonService: EPersonDataService,
|
public epersonService: EPersonDataService,
|
||||||
public groupsDataService: GroupDataService,
|
public groupsDataService: GroupDataService,
|
||||||
private formBuilderService: FormBuilderService,
|
private formBuilderService: FormBuilderService,
|
||||||
@@ -177,7 +180,9 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
private authorizationService: AuthorizationDataService,
|
private authorizationService: AuthorizationDataService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
public requestService: RequestService) {
|
public requestService: RequestService,
|
||||||
|
private epersonRegistrationService: EpersonRegistrationService,
|
||||||
|
) {
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
this.epersonInitial = eperson;
|
this.epersonInitial = eperson;
|
||||||
if (hasValue(eperson)) {
|
if (hasValue(eperson)) {
|
||||||
@@ -310,6 +315,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.canDelete$ = activeEPerson$.pipe(
|
this.canDelete$ = activeEPerson$.pipe(
|
||||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
||||||
);
|
);
|
||||||
|
this.canReset$ = observableOf(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +485,26 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.isImpersonated = false;
|
this.isImpersonated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an email to current eperson address with the information
|
||||||
|
* to reset password
|
||||||
|
*/
|
||||||
|
resetPassword() {
|
||||||
|
if (hasValue(this.epersonInitial.email)) {
|
||||||
|
this.epersonRegistrationService.registerEmail(this.epersonInitial.email).pipe(getFirstCompletedRemoteData())
|
||||||
|
.subscribe((response: RemoteData<Registration>) => {
|
||||||
|
if (response.hasSucceeded) {
|
||||||
|
this.notificationsService.success(this.translateService.get('admin.access-control.epeople.actions.reset'),
|
||||||
|
this.translateService.get('forgot-email.form.success.content', {email: this.epersonInitial.email}));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get('forgot-email.form.error.head'),
|
||||||
|
this.translateService.get('forgot-email.form.error.content', {email: this.epersonInitial.email}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
*/
|
*/
|
||||||
|
@@ -8,6 +8,7 @@ import { SharedModule } from '../../shared/shared.module';
|
|||||||
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
|
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
|
||||||
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
|
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
|
||||||
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
|
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -15,7 +16,8 @@ import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.mo
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
BitstreamFormatsModule,
|
BitstreamFormatsModule,
|
||||||
AdminRegistriesRoutingModule
|
AdminRegistriesRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MetadataRegistryComponent,
|
MetadataRegistryComponent,
|
||||||
|
@@ -7,13 +7,15 @@ import { FormatFormComponent } from './format-form/format-form.component';
|
|||||||
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
||||||
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
|
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
|
||||||
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
||||||
|
import { FormModule } from '../../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
BitstreamFormatsRoutingModule
|
BitstreamFormatsRoutingModule,
|
||||||
|
FormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BitstreamFormatsComponent,
|
BitstreamFormatsComponent,
|
||||||
|
@@ -10,6 +10,7 @@ import { CollectionAdminSearchResultGridElementComponent } from './admin-search-
|
|||||||
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
||||||
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -24,6 +25,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
SearchModule,
|
||||||
SharedModule.withEntryComponents(),
|
SharedModule.withEntryComponents(),
|
||||||
JournalEntitiesModule.withEntryComponents(),
|
JournalEntitiesModule.withEntryComponents(),
|
||||||
ResearchEntitiesModule.withEntryComponents()
|
ResearchEntitiesModule.withEntryComponents()
|
||||||
|
@@ -18,6 +18,8 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
import createSpy = jasmine.createSpy;
|
import createSpy = jasmine.createSpy;
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
describe('AdminSidebarComponent', () => {
|
describe('AdminSidebarComponent', () => {
|
||||||
let comp: AdminSidebarComponent;
|
let comp: AdminSidebarComponent;
|
||||||
@@ -26,6 +28,28 @@ describe('AdminSidebarComponent', () => {
|
|||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let scriptService;
|
let scriptService;
|
||||||
|
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'fake-id',
|
||||||
|
uuid: 'fake-id',
|
||||||
|
handle: 'fake/handle',
|
||||||
|
lastModified: '2018',
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://localhost:8000/items/fake-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
dso: createSuccessfulRemoteDataObject(mockItem)
|
||||||
|
}),
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: observableOf(true)
|
isAuthorized: observableOf(true)
|
||||||
@@ -42,6 +66,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
{ provide: ActivatedRoute, useValue: {} },
|
{ provide: ActivatedRoute, useValue: {} },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: ScriptDataService, useValue: scriptService },
|
{ provide: ScriptDataService, useValue: scriptService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
{
|
{
|
||||||
provide: NgbModal, useValue: {
|
provide: NgbModal, useValue: {
|
||||||
open: () => {/*comment*/
|
open: () => {/*comment*/
|
||||||
|
@@ -21,6 +21,7 @@ import { MenuService } from '../../shared/menu/menu.service';
|
|||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the admin sidebar
|
* Component representing the admin sidebar
|
||||||
@@ -67,10 +68,11 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
private variableService: CSSVariableService,
|
private variableService: CSSVariableService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private authorizationService: AuthorizationDataService,
|
public authorizationService: AuthorizationDataService,
|
||||||
private scriptDataService: ScriptDataService,
|
private scriptDataService: ScriptDataService,
|
||||||
|
public route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
super(menuService, injector);
|
super(menuService, injector, authorizationService, route);
|
||||||
this.inFocus$ = new BehaviorSubject(false);
|
this.inFocus$ = new BehaviorSubject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from './adm
|
|||||||
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
||||||
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -14,6 +15,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
SearchModule,
|
||||||
SharedModule.withEntryComponents()
|
SharedModule.withEntryComponents()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -24,7 +24,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
AccessControlModule,
|
AccessControlModule,
|
||||||
AdminSearchModule.withEntryComponents(),
|
AdminSearchModule.withEntryComponents(),
|
||||||
AdminWorkflowModuleModule.withEntryComponents(),
|
AdminWorkflowModuleModule.withEntryComponents(),
|
||||||
SharedModule,
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AdminCurationTasksComponent,
|
AdminCurationTasksComponent,
|
||||||
|
@@ -36,6 +36,8 @@ import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
|||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
import { APP_CONFIG } from '../config/app-config.interface';
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
let comp: AppComponent;
|
let comp: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
@@ -83,6 +85,7 @@ describe('App component', () => {
|
|||||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
provideMockStore({ initialState }),
|
provideMockStore({ initialState }),
|
||||||
AppComponent,
|
AppComponent,
|
||||||
RouteService
|
RouteService
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators';
|
||||||
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@@ -17,8 +18,10 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||||
|
|
||||||
@@ -39,13 +42,12 @@ import { LocaleService } from './core/locale/locale.service';
|
|||||||
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { getDefaultThemeConfig } from '../config/config.util';
|
||||||
|
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-app',
|
selector: 'ds-app',
|
||||||
@@ -59,7 +61,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
collapsedSidebarWidth: Observable<string>;
|
collapsedSidebarWidth: Observable<string>;
|
||||||
totalSidebarWidth: Observable<string>;
|
totalSidebarWidth: Observable<string>;
|
||||||
theme: Observable<ThemeConfig> = of({} as any);
|
theme: Observable<ThemeConfig> = of({} as any);
|
||||||
notificationOptions = environment.notifications;
|
notificationOptions;
|
||||||
models;
|
models;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +90,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
|
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private store: Store<HostWindowState>,
|
private store: Store<HostWindowState>,
|
||||||
@@ -106,6 +109,12 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
if (!isEqual(environment, this.appConfig)) {
|
||||||
|
throw new Error('environment does not match app config!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notificationOptions = environment.notifications;
|
||||||
|
|
||||||
/* Use models object so all decorators are actually called */
|
/* Use models object so all decorators are actually called */
|
||||||
this.models = models;
|
this.models = models;
|
||||||
|
|
||||||
@@ -116,11 +125,14 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
if (hasValue(themeName)) {
|
if (hasValue(themeName)) {
|
||||||
this.loadGlobalThemeConfig(themeName);
|
this.loadGlobalThemeConfig(themeName);
|
||||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
} else {
|
||||||
this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name);
|
const defaultThemeConfig = getDefaultThemeConfig();
|
||||||
|
if (hasValue(defaultThemeConfig)) {
|
||||||
|
this.loadGlobalThemeConfig(defaultThemeConfig.name);
|
||||||
} else {
|
} else {
|
||||||
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
@@ -305,8 +317,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
// inherit the head tags of the parent theme
|
// inherit the head tags of the parent theme
|
||||||
return this.createHeadTags(parentThemeName);
|
return this.createHeadTags(parentThemeName);
|
||||||
}
|
}
|
||||||
|
const defaultThemeConfig = getDefaultThemeConfig();
|
||||||
const defaultThemeName = DEFAULT_THEME_CONFIG.name;
|
const defaultThemeName = defaultThemeConfig.name;
|
||||||
if (
|
if (
|
||||||
hasNoValue(defaultThemeName) ||
|
hasNoValue(defaultThemeName) ||
|
||||||
themeName === defaultThemeName ||
|
themeName === defaultThemeName ||
|
||||||
@@ -326,7 +338,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// inherit the head tags of the default theme
|
// inherit the head tags of the default theme
|
||||||
return this.createHeadTags(DEFAULT_THEME_CONFIG.name);
|
return this.createHeadTags(defaultThemeConfig.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headTagConfigs.map(this.createHeadTag.bind(this));
|
return headTagConfigs.map(this.createHeadTag.bind(this));
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { AbstractControl } from '@angular/forms';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
@@ -37,7 +39,6 @@ import { NotificationsBoardComponent } from './shared/notifications/notification
|
|||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||||
@@ -54,16 +55,18 @@ import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.com
|
|||||||
import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component';
|
import { ThemedHeaderNavbarWrapperComponent } from './header-nav-wrapper/themed-header-navbar-wrapper.component';
|
||||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
|
|
||||||
import { UUIDService } from './core/shared/uuid.service';
|
import { AppConfig, APP_CONFIG } from '../config/app-config.interface';
|
||||||
import { CookieService } from './core/services/cookie.service';
|
|
||||||
import { AbstractControl } from '@angular/forms';
|
|
||||||
|
|
||||||
export function getBase() {
|
export function getConfig() {
|
||||||
return environment.ui.nameSpace;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMetaReducers(): MetaReducer<AppState>[] {
|
export function getBase(appConfig: AppConfig) {
|
||||||
return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
|
return appConfig.ui.nameSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMetaReducers(appConfig: AppConfig): MetaReducer<AppState>[] {
|
||||||
|
return appConfig.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,13 +101,19 @@ IMPORTS.push(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
|
{
|
||||||
|
provide: APP_CONFIG,
|
||||||
|
useFactory: getConfig
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: APP_BASE_HREF,
|
provide: APP_BASE_HREF,
|
||||||
useFactory: (getBase)
|
useFactory: getBase,
|
||||||
|
deps: [APP_CONFIG]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: USER_PROVIDED_META_REDUCERS,
|
provide: USER_PROVIDED_META_REDUCERS,
|
||||||
useFactory: getMetaReducers,
|
useFactory: getMetaReducers,
|
||||||
|
deps: [APP_CONFIG]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: RouterStateSerializer,
|
provide: RouterStateSerializer,
|
||||||
@@ -144,21 +153,6 @@ const PROVIDERS = [
|
|||||||
useClass: LogInterceptor,
|
useClass: LogInterceptor,
|
||||||
multi: true
|
multi: true
|
||||||
},
|
},
|
||||||
// insert the unique id of the user that is using the application utilizing cookies
|
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
useFactory: (cookieService: CookieService, uuidService: UUIDService) => {
|
|
||||||
const correlationId = cookieService.get('CORRELATION-ID');
|
|
||||||
|
|
||||||
// Check if cookie exists, if don't, set it with unique id
|
|
||||||
if (!correlationId) {
|
|
||||||
cookieService.set('CORRELATION-ID', uuidService.generate());
|
|
||||||
}
|
|
||||||
return () => true;
|
|
||||||
},
|
|
||||||
multi: true,
|
|
||||||
deps: [ CookieService, UUIDService ]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
||||||
useValue: ValidateEmailErrorStateMatcher
|
useValue: ValidateEmailErrorStateMatcher
|
||||||
@@ -195,7 +189,7 @@ const EXPORTS = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
BrowserModule.withServerTransition({ appId: 'dspace-angular' }),
|
||||||
...IMPORTS
|
...IMPORTS
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -49,6 +49,7 @@ import {
|
|||||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||||
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
||||||
|
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
@@ -69,6 +70,7 @@ export interface AppState {
|
|||||||
communityList: CommunityListState;
|
communityList: CommunityListState;
|
||||||
epeopleRegistry: EPeopleRegistryState;
|
epeopleRegistry: EPeopleRegistryState;
|
||||||
groupRegistry: GroupRegistryState;
|
groupRegistry: GroupRegistryState;
|
||||||
|
correlationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -90,6 +92,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
communityList: CommunityListReducer,
|
communityList: CommunityListReducer,
|
||||||
epeopleRegistry: ePeopleRegistryReducer,
|
epeopleRegistry: ePeopleRegistryReducer,
|
||||||
groupRegistry: groupRegistryReducer,
|
groupRegistry: groupRegistryReducer,
|
||||||
|
correlationId: correlationIdReducer
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerStateSelector = (state: AppState) => state.router;
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
@@ -4,6 +4,8 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
|
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
|
||||||
import { BitstreamPageRoutingModule } from './bitstream-page-routing.module';
|
import { BitstreamPageRoutingModule } from './bitstream-page-routing.module';
|
||||||
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
||||||
|
import { FormModule } from '../shared/form/form.module';
|
||||||
|
import { ResourcePoliciesModule } from '../shared/resource-policies/resource-policies.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module handles all components that are necessary for Bitstream related pages
|
* This module handles all components that are necessary for Bitstream related pages
|
||||||
@@ -12,7 +14,9 @@ import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bit
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
BitstreamPageRoutingModule
|
BitstreamPageRoutingModule,
|
||||||
|
FormModule,
|
||||||
|
ResourcePoliciesModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BitstreamAuthorizationsComponent,
|
BitstreamAuthorizationsComponent,
|
||||||
|
@@ -12,7 +12,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
|||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
@@ -29,13 +29,13 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
|
|||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'dateissued' for 'dc.date.issued'
|
* An example would be 'dateissued' for 'dc.date.issued'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Date)
|
@rendersBrowseBy(BrowseByDataType.Date)
|
||||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default metadata-field to use for determining the lower limit of the StartsWith dropdown options
|
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
||||||
*/
|
*/
|
||||||
defaultMetadataField = 'dc.date.issued';
|
defaultMetadataKeys = ['dc.date.issued'];
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
@@ -59,13 +59,13 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
const metadataField = params.metadataField || this.defaultMetadataField;
|
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||||
this.updatePageWithItems(searchOptions, this.value);
|
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
this.updateStartsWithOptions(this.browseId, metadataField, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,15 +76,15 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
* extremely long lists with a one-year difference.
|
* extremely long lists with a one-year difference.
|
||||||
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
||||||
* @param definition The metadata definition to fetch the first item for
|
* @param definition The metadata definition to fetch the first item for
|
||||||
* @param metadataField The metadata field to fetch the earliest date from (expects a date field)
|
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||||
* @param scope The scope under which to fetch the earliest item for
|
* @param scope The scope under which to fetch the earliest item for
|
||||||
*/
|
*/
|
||||||
updateStartsWithOptions(definition: string, metadataField: string, scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
||||||
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
||||||
if (hasValue(firstItemRD.payload)) {
|
if (hasValue(firstItemRD.payload)) {
|
||||||
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
||||||
if (hasValue(date)) {
|
if (hasValue(date)) {
|
||||||
const dateObj = new Date(date);
|
const dateObj = new Date(date);
|
||||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||||
@@ -120,5 +120,4 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,25 @@
|
|||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
describe('BrowseByGuard', () => {
|
describe('BrowseByGuard', () => {
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
let guard: BrowseByGuard;
|
let guard: BrowseByGuard;
|
||||||
let dsoService: any;
|
let dsoService: any;
|
||||||
let translateService: any;
|
let translateService: any;
|
||||||
|
let browseDefinitionService: any;
|
||||||
|
|
||||||
const name = 'An interesting DSO';
|
const name = 'An interesting DSO';
|
||||||
const title = 'Author';
|
const title = 'Author';
|
||||||
const field = 'Author';
|
const field = 'Author';
|
||||||
const id = 'author';
|
const id = 'author';
|
||||||
const metadataField = 'dc.contributor';
|
|
||||||
const scope = '1234-65487-12354-1235';
|
const scope = '1234-65487-12354-1235';
|
||||||
const value = 'Filter';
|
const value = 'Filter';
|
||||||
|
const browseDefinition = Object.assign(new BrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dsoService = {
|
dsoService = {
|
||||||
@@ -24,14 +29,19 @@ describe('BrowseByGuard', () => {
|
|||||||
translateService = {
|
translateService = {
|
||||||
instant: () => field
|
instant: () => field
|
||||||
};
|
};
|
||||||
guard = new BrowseByGuard(dsoService, translateService);
|
|
||||||
|
browseDefinitionService = {
|
||||||
|
findById: () => createSuccessfulRemoteDataObject$(browseDefinition)
|
||||||
|
};
|
||||||
|
|
||||||
|
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||||
const scopedRoute = {
|
const scopedRoute = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -48,7 +58,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: name,
|
collection: name,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
@@ -63,7 +73,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const scopedNoValueRoute = {
|
const scopedNoValueRoute = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -80,7 +90,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: name,
|
collection: name,
|
||||||
field,
|
field,
|
||||||
value: ''
|
value: ''
|
||||||
@@ -95,7 +105,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const route = {
|
const route = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -111,7 +121,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const result = {
|
const result = {
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
metadataField,
|
browseDefinition,
|
||||||
collection: '',
|
collection: '',
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
|
@@ -2,11 +2,12 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angul
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
||||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||||
import { map } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { environment } from '../../environments/environment';
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
/**
|
/**
|
||||||
@@ -15,42 +16,46 @@ import { environment } from '../../environments/environment';
|
|||||||
export class BrowseByGuard implements CanActivate {
|
export class BrowseByGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(protected dsoService: DSpaceObjectDataService,
|
constructor(protected dsoService: DSpaceObjectDataService,
|
||||||
protected translate: TranslateService) {
|
protected translate: TranslateService,
|
||||||
|
protected browseDefinitionService: BrowseDefinitionDataService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
const title = route.data.title;
|
const title = route.data.title;
|
||||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||||
let metadataField = route.data.metadataField;
|
let browseDefinition$: Observable<BrowseDefinition>;
|
||||||
if (hasNoValue(metadataField) && hasValue(id)) {
|
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||||
const config = environment.browseBy.types.find((conf) => conf.id === id);
|
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||||
if (hasValue(config) && hasValue(config.metadataField)) {
|
} else {
|
||||||
metadataField = config.metadataField;
|
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const scope = route.queryParams.scope;
|
const scope = route.queryParams.scope;
|
||||||
const value = route.queryParams.value;
|
const value = route.queryParams.value;
|
||||||
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
|
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
|
||||||
|
return browseDefinition$.pipe(
|
||||||
|
switchMap((browseDefinition) => {
|
||||||
if (hasValue(scope)) {
|
if (hasValue(scope)) {
|
||||||
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
|
||||||
return dsoAndMetadata$.pipe(
|
return dsoAndMetadata$.pipe(
|
||||||
map((dsoRD) => {
|
map((dsoRD) => {
|
||||||
const name = dsoRD.payload.name;
|
const name = dsoRD.payload.name;
|
||||||
route.data = this.createData(title, id, metadataField, name, metadataTranslated, value, route);
|
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
route.data = this.createData(title, id, metadataField, '', metadataTranslated, value, route);
|
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
||||||
return observableOf(true);
|
return observableOf(true);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createData(title, id, metadataField, collection, field, value, route) {
|
private createData(title, id, browseDefinition, collection, field, value, route) {
|
||||||
return Object.assign({}, route.data, {
|
return Object.assign({}, route.data, {
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
metadataField: metadataField,
|
browseDefinition: browseDefinition,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
field: field,
|
field: field,
|
||||||
value: hasValue(value) ? `"${value}"` : ''
|
value: hasValue(value) ? `"${value}"` : ''
|
||||||
|
@@ -14,7 +14,7 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ import { map } from 'rxjs/operators';
|
|||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'author' for 'dc.contributor.*'
|
* An example would be 'author' for 'dc.contributor.*'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Metadata)
|
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||||
export class BrowseByMetadataPageComponent implements OnInit {
|
export class BrowseByMetadataPageComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +99,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
value = '';
|
value = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authority key (may be undefined) associated with {@link #value}.
|
||||||
|
*/
|
||||||
|
authority: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current startsWith option (fetched and updated from query-params)
|
* The current startsWith option (fetched and updated from query-params)
|
||||||
*/
|
*/
|
||||||
@@ -123,11 +128,12 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
|
this.authority = params.authority;
|
||||||
this.value = +params.value || params.value || '';
|
this.value = +params.value || params.value || '';
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
||||||
if (isNotEmpty(this.value)) {
|
if (isNotEmpty(this.value)) {
|
||||||
this.updatePageWithItems(searchOptions, this.value);
|
this.updatePageWithItems(searchOptions, this.value, this.authority);
|
||||||
} else {
|
} else {
|
||||||
this.updatePage(searchOptions);
|
this.updatePage(searchOptions);
|
||||||
}
|
}
|
||||||
@@ -166,8 +172,8 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
* scope: string }
|
* scope: string }
|
||||||
* @param value The value of the browse-entry to display items for
|
* @param value The value of the browse-entry to display items for
|
||||||
*/
|
*/
|
||||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string) {
|
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
||||||
this.items$ = this.browseService.getBrowseItemsFor(value, searchOptions);
|
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { BrowseByType, rendersBrowseBy } from './browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator';
|
||||||
|
|
||||||
describe('BrowseByDecorator', () => {
|
describe('BrowseByDecorator', () => {
|
||||||
const titleDecorator = rendersBrowseBy(BrowseByType.Title);
|
const titleDecorator = rendersBrowseBy(BrowseByDataType.Title);
|
||||||
const dateDecorator = rendersBrowseBy(BrowseByType.Date);
|
const dateDecorator = rendersBrowseBy(BrowseByDataType.Date);
|
||||||
const metadataDecorator = rendersBrowseBy(BrowseByType.Metadata);
|
const metadataDecorator = rendersBrowseBy(BrowseByDataType.Metadata);
|
||||||
it('should have a decorator for all types', () => {
|
it('should have a decorator for all types', () => {
|
||||||
expect(titleDecorator.length).not.toEqual(0);
|
expect(titleDecorator.length).not.toEqual(0);
|
||||||
expect(dateDecorator.length).not.toEqual(0);
|
expect(dateDecorator.length).not.toEqual(0);
|
||||||
|
@@ -2,13 +2,13 @@ import { hasNoValue } from '../../shared/empty.util';
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
|
||||||
export enum BrowseByType {
|
export enum BrowseByDataType {
|
||||||
Title = 'title',
|
Title = 'title',
|
||||||
Metadata = 'metadata',
|
Metadata = 'text',
|
||||||
Date = 'date'
|
Date = 'date'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata;
|
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
||||||
|
|
||||||
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -21,7 +21,7 @@ const map = new Map();
|
|||||||
* Decorator used for rendering Browse-By pages by type
|
* Decorator used for rendering Browse-By pages by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
*/
|
*/
|
||||||
export function rendersBrowseBy(browseByType: BrowseByType) {
|
export function rendersBrowseBy(browseByType: BrowseByDataType) {
|
||||||
return function decorator(component: any) {
|
return function decorator(component: any) {
|
||||||
if (hasNoValue(map.get(browseByType))) {
|
if (hasNoValue(map.get(browseByType))) {
|
||||||
map.set(browseByType, component);
|
map.set(browseByType, component);
|
||||||
|
@@ -2,20 +2,46 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('BrowseBySwitcherComponent', () => {
|
describe('BrowseBySwitcherComponent', () => {
|
||||||
let comp: BrowseBySwitcherComponent;
|
let comp: BrowseBySwitcherComponent;
|
||||||
let fixture: ComponentFixture<BrowseBySwitcherComponent>;
|
let fixture: ComponentFixture<BrowseBySwitcherComponent>;
|
||||||
|
|
||||||
const types = environment.browseBy.types;
|
const types = [
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'title',
|
||||||
|
dataType: BrowseByDataType.Title,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'dateissued',
|
||||||
|
dataType: BrowseByDataType.Date,
|
||||||
|
metadataKeys: ['dc.date.issued']
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'author',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Object.assign(
|
||||||
|
new BrowseDefinition(), {
|
||||||
|
id: 'subject',
|
||||||
|
dataType: BrowseByDataType.Metadata,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
const params = new BehaviorSubject(createParamsWithId('initialValue'));
|
const data = new BehaviorSubject(createDataWithBrowseDefinition(new BrowseDefinition()));
|
||||||
|
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
params: params
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
@@ -34,20 +60,20 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
types.forEach((type) => {
|
types.forEach((type: BrowseDefinition) => {
|
||||||
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
params.next(createParamsWithId(type.id));
|
data.next(createDataWithBrowseDefinition(type));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should call getComponentByBrowseByType with type "${type.type}"`, () => {
|
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
||||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.type);
|
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createParamsWithId(id) {
|
export function createDataWithBrowseDefinition(browseDefinition) {
|
||||||
return { id: id };
|
return { browseDefinition: browseDefinition };
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
|
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-switcher',
|
selector: 'ds-browse-by-switcher',
|
||||||
@@ -26,15 +25,11 @@ export class BrowseBySwitcherComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the correct browse-by component by using the relevant config from environment.js
|
* Fetch the correct browse-by component by using the relevant config from the route data
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByComponent = this.route.params.pipe(
|
this.browseByComponent = this.route.data.pipe(
|
||||||
map((params) => {
|
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType))
|
||||||
const id = params.id;
|
|
||||||
return environment.browseBy.types.find((config: BrowseByTypeConfig) => config.id === id);
|
|
||||||
}),
|
|
||||||
map((config: BrowseByTypeConfig) => this.getComponentByBrowseByType(config.type))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { BrowseByType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -23,7 +23,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
|||||||
/**
|
/**
|
||||||
* Component for browsing items by title (dc.title)
|
* Component for browsing items by title (dc.title)
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByType.Title)
|
@rendersBrowseBy(BrowseByDataType.Title)
|
||||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
@@ -46,7 +46,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined);
|
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
@@ -6,6 +6,7 @@ import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-
|
|||||||
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -17,6 +18,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
ComcolModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
@@ -28,8 +28,8 @@ import { NONE_ENTITY_TYPE } from '../../core/shared/item-relationships/item-type
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-form',
|
selector: 'ds-collection-form',
|
||||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||||
})
|
})
|
||||||
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
||||||
/**
|
/**
|
||||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { CollectionFormComponent } from './collection-form.component';
|
import { CollectionFormComponent } from './collection-form.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
|
FormModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -33,7 +33,7 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
|||||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import {
|
import {
|
||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
|
@@ -9,10 +9,11 @@ import { Collection } from '../../core/shared/collection.model';
|
|||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
getRemoteDataPayload,
|
getAllSucceededRemoteData,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
toDSpaceObjectListRD,
|
getRemoteDataPayload,
|
||||||
getFirstCompletedRemoteData, getAllSucceededRemoteData
|
toDSpaceObjectListRD
|
||||||
} from '../../core/shared/operators';
|
} from '../../core/shared/operators';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||||
@@ -24,7 +25,7 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
|||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchService } from '../../core/shared/search/search.service';
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
import { NoContent } from '../../core/shared/NoContent.model';
|
import { NoContent } from '../../core/shared/NoContent.model';
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import {
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs';
|
||||||
BehaviorSubject,
|
|
||||||
combineLatest as observableCombineLatest,
|
|
||||||
Observable,
|
|
||||||
Subject
|
|
||||||
} from 'rxjs';
|
|
||||||
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
|
@@ -14,6 +14,7 @@ import { SearchService } from '../core/shared/search/search.service';
|
|||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CollectionFormModule } from './collection-form/collection-form.module';
|
import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,7 +23,8 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
|
|||||||
CollectionPageRoutingModule,
|
CollectionPageRoutingModule,
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
EditItemPageModule,
|
EditItemPageModule,
|
||||||
CollectionFormModule
|
CollectionFormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
|
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
|||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { RouteService } from '../../core/services/route.service';
|
import { RouteService } from '../../core/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
@@ -12,6 +12,7 @@ import { RequestService } from '../../../core/data/request.service';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
describe('CollectionRolesComponent', () => {
|
describe('CollectionRolesComponent', () => {
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ describe('CollectionRolesComponent', () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import { getCollectionPageRoute } from '../collection-page-routing-paths';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-collection',
|
selector: 'ds-edit-collection',
|
||||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||||
})
|
})
|
||||||
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
||||||
type = 'collection';
|
type = 'collection';
|
||||||
|
@@ -10,6 +10,9 @@ import { CollectionSourceComponent } from './collection-source/collection-source
|
|||||||
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||||
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
import { CollectionFormModule } from '../collection-form/collection-form.module';
|
||||||
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
import { CollectionSourceControlsComponent } from './collection-source/collection-source-controls/collection-source-controls.component';
|
||||||
|
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Collection page administrator functionality
|
* Module that contains all components related to the Edit Collection page administrator functionality
|
||||||
@@ -19,7 +22,10 @@ import { CollectionSourceControlsComponent } from './collection-source/collectio
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EditCollectionPageRoutingModule,
|
EditCollectionPageRoutingModule,
|
||||||
CollectionFormModule
|
CollectionFormModule,
|
||||||
|
ResourcePoliciesModule,
|
||||||
|
FormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditCollectionPageComponent,
|
EditCollectionPageComponent,
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
DynamicTextAreaModel
|
DynamicTextAreaModel
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
@@ -19,8 +19,8 @@ import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-form',
|
selector: 'ds-community-form',
|
||||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
styleUrls: ['../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/comcol-form/comcol-form.component.html'
|
||||||
})
|
})
|
||||||
export class CommunityFormComponent extends ComColFormComponent<Community> {
|
export class CommunityFormComponent extends ComColFormComponent<Community> {
|
||||||
/**
|
/**
|
||||||
|
@@ -2,9 +2,13 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { CommunityFormComponent } from './community-form.component';
|
import { CommunityFormComponent } from './community-form.component';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
import { FormModule } from '../../shared/form/form.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
|
FormModule,
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -12,6 +12,7 @@ import { DeleteCommunityPageComponent } from './delete-community-page/delete-com
|
|||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CommunityFormModule } from './community-form/community-form.module';
|
import { CommunityFormModule } from './community-form/community-form.module';
|
||||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
|
||||||
const DECLARATIONS = [CommunityPageComponent,
|
const DECLARATIONS = [CommunityPageComponent,
|
||||||
ThemedCommunityPageComponent,
|
ThemedCommunityPageComponent,
|
||||||
@@ -26,7 +27,8 @@ const DECLARATIONS = [CommunityPageComponent,
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
CommunityPageRoutingModule,
|
CommunityPageRoutingModule,
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
CommunityFormModule
|
CommunityFormModule,
|
||||||
|
ComcolModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
|
@@ -3,7 +3,7 @@ import { Community } from '../../core/shared/community.model';
|
|||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { RouteService } from '../../core/services/route.service';
|
import { RouteService } from '../../core/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
import { CreateComColPageComponent } from '../../shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
|||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
@@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { first, map } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { RemoteData } from 'src/app/core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-authorizations',
|
selector: 'ds-community-authorizations',
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||||
|
@@ -12,6 +12,7 @@ import { SharedModule } from '../../../shared/shared.module';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { ComcolModule } from '../../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
describe('CommunityRolesComponent', () => {
|
describe('CommunityRolesComponent', () => {
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ describe('CommunityRolesComponent', () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
ComcolModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
import { EditComColPageComponent } from '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||||
import { getCommunityPageRoute } from '../community-page-routing-paths';
|
import { getCommunityPageRoute } from '../community-page-routing-paths';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +9,7 @@ import { getCommunityPageRoute } from '../community-page-routing-paths';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-community',
|
selector: 'ds-edit-community',
|
||||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||||
})
|
})
|
||||||
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
||||||
type = 'community';
|
type = 'community';
|
||||||
|
@@ -8,6 +8,8 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
|
|||||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||||
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||||
import { CommunityFormModule } from '../community-form/community-form.module';
|
import { CommunityFormModule } from '../community-form/community-form.module';
|
||||||
|
import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module';
|
||||||
|
import { ComcolModule } from '../../shared/comcol/comcol.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Community page administrator functionality
|
* Module that contains all components related to the Edit Community page administrator functionality
|
||||||
@@ -17,7 +19,9 @@ import { CommunityFormModule } from '../community-form/community-form.module';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EditCommunityPageRoutingModule,
|
EditCommunityPageRoutingModule,
|
||||||
CommunityFormModule
|
CommunityFormModule,
|
||||||
|
ComcolModule,
|
||||||
|
ResourcePoliciesModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditCommunityPageComponent,
|
EditCommunityPageComponent,
|
||||||
|
@@ -9,9 +9,11 @@ describe(`BrowseDefinitionDataService`, () => {
|
|||||||
findAll: EMPTY,
|
findAll: EMPTY,
|
||||||
findByHref: EMPTY,
|
findByHref: EMPTY,
|
||||||
findAllByHref: EMPTY,
|
findAllByHref: EMPTY,
|
||||||
|
findById: EMPTY,
|
||||||
});
|
});
|
||||||
const hrefAll = 'https://rest.api/server/api/discover/browses';
|
const hrefAll = 'https://rest.api/server/api/discover/browses';
|
||||||
const hrefSingle = 'https://rest.api/server/api/discover/browses/author';
|
const hrefSingle = 'https://rest.api/server/api/discover/browses/author';
|
||||||
|
const id = 'author';
|
||||||
const options = new FindListOptions();
|
const options = new FindListOptions();
|
||||||
const linksToFollow = [
|
const linksToFollow = [
|
||||||
followLink('entries'),
|
followLink('entries'),
|
||||||
@@ -44,4 +46,10 @@ describe(`BrowseDefinitionDataService`, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`findById`, () => {
|
||||||
|
it(`should call findById on DataServiceImpl`, () => {
|
||||||
|
service.findAllByHref(id, options, true, false, ...linksToFollow);
|
||||||
|
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(id, options, true, false, ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -106,6 +106,21 @@ export class BrowseDefinitionDataService {
|
|||||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
|
||||||
|
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||||
|
* @param id ID of object we want to retrieve
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
|
||||||
|
return this.dataService.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -129,6 +129,7 @@ describe('BrowseService', () => {
|
|||||||
describe('getBrowseEntriesFor and findList', () => {
|
describe('getBrowseEntriesFor and findList', () => {
|
||||||
// should contain special characters such that url encoding can be tested as well
|
// should contain special characters such that url encoding can be tested as well
|
||||||
const mockAuthorName = 'Donald Smith & Sons';
|
const mockAuthorName = 'Donald Smith & Sons';
|
||||||
|
const mockAuthorityKey = 'some authority key ?=;';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
@@ -155,7 +156,7 @@ describe('BrowseService', () => {
|
|||||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||||
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, undefined, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||||
@@ -164,6 +165,20 @@ describe('BrowseService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
describe('when getBrowseItemsFor is called with a valid filter value and authority key', () => {
|
||||||
|
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||||
|
const expected = browseDefinitions[1]._links.items.href +
|
||||||
|
'?filterValue=' + encodeURIComponent(mockAuthorName) +
|
||||||
|
'&filterAuthority=' + encodeURIComponent(mockAuthorityKey);
|
||||||
|
|
||||||
|
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, mockAuthorityKey, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||||
|
a: expected
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseURLFor', () => {
|
describe('getBrowseURLFor', () => {
|
||||||
|
@@ -105,7 +105,7 @@ export class BrowseService {
|
|||||||
* @param options Options to narrow down your search
|
* @param options Options to narrow down your search
|
||||||
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||||
*/
|
*/
|
||||||
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
getBrowseItemsFor(filterValue: string, filterAuthority: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
const href$ = this.getBrowseDefinitions().pipe(
|
const href$ = this.getBrowseDefinitions().pipe(
|
||||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
@@ -132,6 +132,9 @@ export class BrowseService {
|
|||||||
if (isNotEmpty(filterValue)) {
|
if (isNotEmpty(filterValue)) {
|
||||||
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
||||||
}
|
}
|
||||||
|
if (isNotEmpty(filterAuthority)) {
|
||||||
|
args.push(`filterAuthority=${encodeURIComponent(filterAuthority)}`);
|
||||||
|
}
|
||||||
if (isNotEmpty(args)) {
|
if (isNotEmpty(args)) {
|
||||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
}
|
}
|
||||||
|
@@ -159,6 +159,7 @@ import { RootDataService } from './data/root-data.service';
|
|||||||
import { Root } from './data/root.model';
|
import { Root } from './data/root.model';
|
||||||
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
||||||
import { SequenceService } from './shared/sequence.service';
|
import { SequenceService } from './shared/sequence.service';
|
||||||
|
import { GroupDataService } from './eperson/group-data.service';
|
||||||
import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model';
|
import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,6 +283,7 @@ const PROVIDERS = [
|
|||||||
VocabularyService,
|
VocabularyService,
|
||||||
VocabularyTreeviewService,
|
VocabularyTreeviewService,
|
||||||
SequenceService,
|
SequenceService,
|
||||||
|
GroupDataService
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -20,7 +20,7 @@ import { PaginatedList } from './paginated-list.model';
|
|||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { FindListOptions, GetRequest } from './request.models';
|
import { FindListOptions, GetRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
import { RequestEntryState } from './request.reducer';
|
import { RequestEntryState } from './request.reducer';
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { FindListOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../shared/search/models/search-filter-config.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
import { FacetConfigResponse } from '../../shared/search/facet-config-response.model';
|
import { FacetConfigResponse } from '../../shared/search/models/facet-config-response.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FacetConfigResponseParsingService extends DspaceRestResponseParsingService {
|
export class FacetConfigResponseParsingService extends DspaceRestResponseParsingService {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { FacetValue } from '../../shared/search/facet-value.model';
|
import { FacetValue } from '../../shared/search/models/facet-value.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { FacetValues } from '../../shared/search/facet-values.model';
|
import { FacetValues } from '../../shared/search/models/facet-values.model';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard';
|
||||||
|
import { AuthorizationDataService } from '../authorization-data.service';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { FeatureID } from '../feature-id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group
|
||||||
|
* management rights
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class StatisticsAdministratorGuard extends SingleFeatureAuthorizationGuard {
|
||||||
|
constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
|
||||||
|
super(authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check group management rights
|
||||||
|
*/
|
||||||
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
|
return observableOf(FeatureID.CanViewUsageStatistics);
|
||||||
|
}
|
||||||
|
}
|
@@ -13,6 +13,7 @@ export enum FeatureID {
|
|||||||
CanManageGroup = 'canManageGroup',
|
CanManageGroup = 'canManageGroup',
|
||||||
IsCollectionAdmin = 'isCollectionAdmin',
|
IsCollectionAdmin = 'isCollectionAdmin',
|
||||||
IsCommunityAdmin = 'isCommunityAdmin',
|
IsCommunityAdmin = 'isCommunityAdmin',
|
||||||
|
CanChangePassword = 'canChangePassword',
|
||||||
CanDownload = 'canDownload',
|
CanDownload = 'canDownload',
|
||||||
CanRequestACopy = 'canRequestACopy',
|
CanRequestACopy = 'canRequestACopy',
|
||||||
CanManageVersions = 'canManageVersions',
|
CanManageVersions = 'canManageVersions',
|
||||||
@@ -25,4 +26,5 @@ export enum FeatureID {
|
|||||||
CanEditVersion = 'canEditVersion',
|
CanEditVersion = 'canEditVersion',
|
||||||
CanDeleteVersion = 'canDeleteVersion',
|
CanDeleteVersion = 'canDeleteVersion',
|
||||||
CanCreateVersion = 'canCreateVersion',
|
CanCreateVersion = 'canCreateVersion',
|
||||||
|
CanViewUsageStatistics = 'canViewUsageStatistics',
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ import { PaginatedList } from './paginated-list.model';
|
|||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { DeleteRequest, FindListOptions, GetRequest, PostRequest, PutRequest, RestRequest } from './request.models';
|
import { DeleteRequest, FindListOptions, GetRequest, PostRequest, PutRequest, RestRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Bundle } from '../shared/bundle.model';
|
import { Bundle } from '../shared/bundle.model';
|
||||||
import { MetadataMap } from '../shared/metadata.models';
|
import { MetadataMap } from '../shared/metadata.models';
|
||||||
import { BundleDataService } from './bundle-data.service';
|
import { BundleDataService } from './bundle-data.service';
|
||||||
|
@@ -5,9 +5,9 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.util
|
|||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { buildPaginatedList } from './paginated-list.model';
|
import { buildPaginatedList } from './paginated-list.model';
|
||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import { skip, take } from 'rxjs/operators';
|
import { skip, take } from 'rxjs/operators';
|
||||||
import { ExternalSource } from '../shared/external-source.model';
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { ExternalSourceService } from './external-source.service';
|
import { ExternalSourceService } from './external-source.service';
|
||||||
import { SearchService } from '../shared/search/search.service';
|
import { SearchService } from '../shared/search/search.service';
|
||||||
import { concat, distinctUntilChanged, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
import { concat, distinctUntilChanged, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { Observable, ReplaySubject } from 'rxjs';
|
import { Observable, ReplaySubject } from 'rxjs';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
|
@@ -4,7 +4,7 @@ import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
|||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||||
import { MetadataMap, MetadataValue } from '../shared/metadata.models';
|
import { MetadataMap, MetadataValue } from '../shared/metadata.models';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ export enum IdentifierType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class RestRequest {
|
export abstract class RestRequest {
|
||||||
public responseMsToLive = environment.cache.msToLive.default;
|
public responseMsToLive;
|
||||||
public isMultipart = false;
|
public isMultipart = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -30,6 +30,7 @@ export abstract class RestRequest {
|
|||||||
public body?: any,
|
public body?: any,
|
||||||
public options?: HttpOptions,
|
public options?: HttpOptions,
|
||||||
) {
|
) {
|
||||||
|
this.responseMsToLive = environment.cache.msToLive.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchObjects } from '../../shared/search/search-objects.model';
|
import { SearchObjects } from '../../shared/search/models/search-objects.model';
|
||||||
import { ParsedResponse } from '../cache/response.models';
|
import { ParsedResponse } from '../cache/response.models';
|
||||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
|
@@ -12,7 +12,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { FindListOptions, PostRequest, RestRequest } from './request.models';
|
import { FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { Version } from '../shared/version.model';
|
import { Version } from '../shared/version.model';
|
||||||
|
@@ -40,9 +40,7 @@ const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegis
|
|||||||
/**
|
/**
|
||||||
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable()
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
@dataService(GROUP)
|
@dataService(GROUP)
|
||||||
export class GroupDataService extends DataService<Group> {
|
export class GroupDataService extends DataService<Group> {
|
||||||
protected linkPath = 'groups';
|
protected linkPath = 'groups';
|
||||||
|
@@ -9,12 +9,17 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
|||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
||||||
import { RouterStub } from '../../shared/testing/router.stub';
|
import { RouterStub } from '../../shared/testing/router.stub';
|
||||||
|
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||||
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
import { appReducers, storeModuleConfig } from '../../app.reducer';
|
||||||
|
|
||||||
|
|
||||||
describe('LogInterceptor', () => {
|
describe('LogInterceptor', () => {
|
||||||
let service: DspaceRestService;
|
let service: DspaceRestService;
|
||||||
let httpMock: HttpTestingController;
|
let httpMock: HttpTestingController;
|
||||||
let cookieService: CookieService;
|
let cookieService: CookieService;
|
||||||
|
let correlationIdService: CorrelationIdService;
|
||||||
const router = Object.assign(new RouterStub(),{url : '/statistics'});
|
const router = Object.assign(new RouterStub(),{url : '/statistics'});
|
||||||
|
|
||||||
// Mock payload/statuses are dummy content as we are not testing the results
|
// Mock payload/statuses are dummy content as we are not testing the results
|
||||||
@@ -28,7 +33,10 @@ describe('LogInterceptor', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [HttpClientTestingModule],
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DspaceRestService,
|
DspaceRestService,
|
||||||
// LogInterceptor,
|
// LogInterceptor,
|
||||||
@@ -39,14 +47,18 @@ describe('LogInterceptor', () => {
|
|||||||
},
|
},
|
||||||
{ provide: CookieService, useValue: new CookieServiceMock() },
|
{ provide: CookieService, useValue: new CookieServiceMock() },
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: CorrelationIdService, useClass: CorrelationIdService },
|
||||||
|
{ provide: UUIDService, useClass: UUIDService },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
service = TestBed.get(DspaceRestService);
|
service = TestBed.inject(DspaceRestService);
|
||||||
httpMock = TestBed.get(HttpTestingController);
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
cookieService = TestBed.get(CookieService);
|
cookieService = TestBed.inject(CookieService);
|
||||||
|
correlationIdService = TestBed.inject(CorrelationIdService);
|
||||||
|
|
||||||
cookieService.set('CORRELATION-ID','123455');
|
cookieService.set('CORRELATION-ID','123455');
|
||||||
|
correlationIdService.initCorrelationId();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3,9 +3,8 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { CookieService } from '../services/cookie.service';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { CorrelationIdService } from '../../correlation-id/correlation-id.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log Interceptor intercepting Http Requests & Responses to
|
* Log Interceptor intercepting Http Requests & Responses to
|
||||||
@@ -15,12 +14,12 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class LogInterceptor implements HttpInterceptor {
|
export class LogInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
constructor(private cookieService: CookieService, private router: Router) {}
|
constructor(private cidService: CorrelationIdService, private router: Router) {}
|
||||||
|
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
// Get Unique id of the user from the cookies
|
// Get the correlation id for the user from the store
|
||||||
const correlationId = this.cookieService.get('CORRELATION-ID');
|
const correlationId = this.cidService.getCorrelationId();
|
||||||
|
|
||||||
// Add headers from the intercepted request
|
// Add headers from the intercepted request
|
||||||
let headers = request.headers;
|
let headers = request.headers;
|
||||||
|
@@ -6,6 +6,7 @@ import { BROWSE_DEFINITION } from './browse-definition.resource-type';
|
|||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
import { SortOption } from './sort-option.model';
|
import { SortOption } from './sort-option.model';
|
||||||
|
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
export class BrowseDefinition extends CacheableObject {
|
export class BrowseDefinition extends CacheableObject {
|
||||||
@@ -33,6 +34,9 @@ export class BrowseDefinition extends CacheableObject {
|
|||||||
@autoserializeAs('metadata')
|
@autoserializeAs('metadata')
|
||||||
metadataKeys: string[];
|
metadataKeys: string[];
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
dataType: BrowseByDataType;
|
||||||
|
|
||||||
get self(): string {
|
get self(): string {
|
||||||
return this._links.self.href;
|
return this._links.self.href;
|
||||||
}
|
}
|
||||||
|
@@ -89,7 +89,7 @@ describe('HALEndpointService', () => {
|
|||||||
describe('getRootEndpointMap', () => {
|
describe('getRootEndpointMap', () => {
|
||||||
it('should send a new EndpointMapRequest', () => {
|
it('should send a new EndpointMapRequest', () => {
|
||||||
(service as any).getRootEndpointMap();
|
(service as any).getRootEndpointMap();
|
||||||
const expected = new EndpointMapRequest(requestService.generateRequestId(), environment.rest.baseUrl + 'api');
|
const expected = new EndpointMapRequest(requestService.generateRequestId(), `${environment.rest.baseUrl}/api`);
|
||||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
withLatestFrom
|
withLatestFrom
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/models/search-result.model';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { RestRequest } from '../data/request.models';
|
import { RestRequest } from '../data/request.models';
|
||||||
|
@@ -2,8 +2,8 @@ import { SearchConfigurationService } from './search-configuration.service';
|
|||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
|
||||||
|
@@ -3,30 +3,25 @@ import { ActivatedRoute, Params } from '@angular/router';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
merge as observableMerge,
|
merge as observableMerge,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription
|
Subscription
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, startWith } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
import { SearchOptions } from '../../../shared/search/models/search-options.model';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { SearchFilter } from '../../../shared/search/search-filter.model';
|
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
import { DSpaceObjectType } from '../dspace-object-type.model';
|
import { DSpaceObjectType } from '../dspace-object-type.model';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
import { RouteService } from '../../services/route.service';
|
import { RouteService } from '../../services/route.service';
|
||||||
import {
|
import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData } from '../operators';
|
||||||
getAllSucceededRemoteDataPayload,
|
|
||||||
getFirstSucceededRemoteData
|
|
||||||
} from '../operators';
|
|
||||||
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { SearchConfig } from './search-filters/search-config.model';
|
import { SearchConfig, SortConfig } from './search-filters/search-config.model';
|
||||||
import { SearchService } from './search.service';
|
import { SearchService } from './search.service';
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { PaginationService } from '../../pagination/pagination.service';
|
import { PaginationService } from '../../pagination/pagination.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +30,21 @@ import { PaginationService } from '../../pagination/pagination.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchConfigurationService implements OnDestroy {
|
export class SearchConfigurationService implements OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default pagination id
|
||||||
|
*/
|
||||||
public paginationID = 'spc';
|
public paginationID = 'spc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current search options
|
||||||
|
*/
|
||||||
|
public searchOptions: BehaviorSubject<SearchOptions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current search options including pagination and sort
|
||||||
|
*/
|
||||||
|
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default pagination settings
|
* Default pagination settings
|
||||||
*/
|
*/
|
||||||
@@ -45,16 +54,6 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
currentPage: 1
|
currentPage: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Default sort settings
|
|
||||||
*/
|
|
||||||
protected defaultSort = new SortOptions('score', SortDirection.DESC);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default configuration parameter setting
|
|
||||||
*/
|
|
||||||
protected defaultConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default scope setting
|
* Default scope setting
|
||||||
*/
|
*/
|
||||||
@@ -71,23 +70,14 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
protected _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
|
protected _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the current search options
|
* A map of subscriptions to unsubscribe from on destroy
|
||||||
*/
|
*/
|
||||||
public searchOptions: BehaviorSubject<SearchOptions>;
|
protected subs: Map<string, Subscription[]> = new Map<string, Subscription[]>(null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits the current search options including pagination and sort
|
|
||||||
*/
|
|
||||||
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of subscriptions to unsubscribe from on destroy
|
|
||||||
*/
|
|
||||||
protected subs: Subscription[] = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the search options
|
* Initialize the search options
|
||||||
* @param {RouteService} routeService
|
* @param {RouteService} routeService
|
||||||
|
* @param {PaginationService} paginationService
|
||||||
* @param {ActivatedRoute} route
|
* @param {ActivatedRoute} route
|
||||||
*/
|
*/
|
||||||
constructor(protected routeService: RouteService,
|
constructor(protected routeService: RouteService,
|
||||||
@@ -98,29 +88,28 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the search options
|
* Default values for the Search Options
|
||||||
*/
|
*/
|
||||||
protected initDefaults() {
|
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
||||||
this.defaults
|
if (hasNoValue(this._defaults)) {
|
||||||
.pipe(getFirstSucceededRemoteData())
|
const options = new PaginatedSearchOptions({
|
||||||
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
pagination: this.defaultPagination,
|
||||||
const defs = defRD.payload;
|
scope: this.defaultScope,
|
||||||
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
query: this.defaultQuery
|
||||||
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
});
|
||||||
this.subs.push(this.subscribeToSearchOptions(defs));
|
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
||||||
this.subs.push(this.subscribeToPaginatedSearchOptions(defs.pagination.id, defs));
|
|
||||||
}
|
}
|
||||||
);
|
return this._defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current configuration string
|
* @returns {Observable<string>} Emits the current configuration string
|
||||||
*/
|
*/
|
||||||
getCurrentConfiguration(defaultConfiguration: string) {
|
getCurrentConfiguration(defaultConfiguration: string) {
|
||||||
return observableCombineLatest(
|
return observableCombineLatest([
|
||||||
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
this.routeService.getQueryParameterValue('configuration').pipe(startWith(undefined)),
|
||||||
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
this.routeService.getRouteParameterValue('configuration').pipe(startWith(undefined))
|
||||||
).pipe(
|
]).pipe(
|
||||||
map(([queryConfig, routeConfig]) => {
|
map(([queryConfig, routeConfig]) => {
|
||||||
return queryConfig || routeConfig || defaultConfiguration;
|
return queryConfig || routeConfig || defaultConfiguration;
|
||||||
})
|
})
|
||||||
@@ -208,59 +197,82 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an observable of SearchConfig every time the configuration$ stream emits.
|
* Creates an observable of SearchConfig every time the configuration stream emits.
|
||||||
* @param configuration$
|
* @param configuration The search configuration
|
||||||
* @param service
|
* @param service The search service to use
|
||||||
|
* @param scope The search scope if exists
|
||||||
*/
|
*/
|
||||||
getConfigurationSearchConfigObservable(configuration$: Observable<string>, service: SearchService): Observable<SearchConfig> {
|
getConfigurationSearchConfig(configuration: string, service: SearchService, scope?: string): Observable<SearchConfig> {
|
||||||
return configuration$.pipe(
|
return service.getSearchConfigurationFor(scope, configuration).pipe(
|
||||||
distinctUntilChanged(),
|
getAllSucceededRemoteDataPayload()
|
||||||
switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)),
|
);
|
||||||
getAllSucceededRemoteDataPayload());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every time searchConfig change (after a configuration change) it update the navigation with the default sort option
|
* Return the SortOptions list available for the given SearchConfig
|
||||||
* and emit the new paginateSearchOptions value.
|
* @param searchConfig The SearchConfig object
|
||||||
* @param configuration$
|
|
||||||
* @param service
|
|
||||||
*/
|
*/
|
||||||
initializeSortOptionsFromConfiguration(searchConfig$: Observable<SearchConfig>) {
|
getConfigurationSortOptions(searchConfig: SearchConfig): SortOptions[] {
|
||||||
const subscription = searchConfig$.pipe(switchMap((searchConfig) => combineLatest([
|
return searchConfig.sortOptions.map((entry: SortConfig) => ({
|
||||||
of(searchConfig),
|
field: entry.name,
|
||||||
this.paginatedSearchOptions.pipe(take(1))
|
direction: entry.sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC
|
||||||
]))).subscribe(([searchConfig, searchOptions]) => {
|
|
||||||
const field = searchConfig.sortOptions[0].name;
|
|
||||||
const direction = searchConfig.sortOptions[0].sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC;
|
|
||||||
const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, {
|
|
||||||
sort: new SortOptions(field, direction)
|
|
||||||
});
|
|
||||||
this.paginationService.updateRoute(this.paginationID,
|
|
||||||
{
|
|
||||||
sortDirection: updateValue.sort.direction,
|
|
||||||
sortField: updateValue.sort.field,
|
|
||||||
});
|
|
||||||
this.paginatedSearchOptions.next(updateValue);
|
|
||||||
});
|
|
||||||
this.subs.push(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an observable of available SortOptions[] every time the searchConfig$ stream emits.
|
|
||||||
* @param searchConfig$
|
|
||||||
* @param service
|
|
||||||
*/
|
|
||||||
getConfigurationSortOptionsObservable(searchConfig$: Observable<SearchConfig>): Observable<SortOptions[]> {
|
|
||||||
return searchConfig$.pipe(map((searchConfig) => {
|
|
||||||
const sortOptions = [];
|
|
||||||
searchConfig.sortOptions.forEach(sortOption => {
|
|
||||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC));
|
|
||||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC));
|
|
||||||
});
|
|
||||||
return sortOptions;
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPaginationId(paginationId): void {
|
||||||
|
if (isNotEmpty(paginationId)) {
|
||||||
|
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
||||||
|
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, {
|
||||||
|
pagination: Object.assign({}, currentValue.pagination, {
|
||||||
|
id: paginationId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// unsubscribe from subscription related to old pagination id
|
||||||
|
this.unsubscribeFromSearchOptions(this.paginationID);
|
||||||
|
|
||||||
|
// change to the new pagination id
|
||||||
|
this.paginationID = paginationId;
|
||||||
|
this.paginatedSearchOptions.next(updatedValue);
|
||||||
|
this.setSearchSubscription(this.paginationID, this.paginatedSearchOptions.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.forEach((subs: Subscription[]) => subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe())
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subs = new Map<string, Subscription[]>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the search options
|
||||||
|
*/
|
||||||
|
protected initDefaults() {
|
||||||
|
this.defaults
|
||||||
|
.pipe(getFirstSucceededRemoteData())
|
||||||
|
.subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
|
||||||
|
const defs = defRD.payload;
|
||||||
|
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
|
||||||
|
this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
|
||||||
|
this.setSearchSubscription(this.paginationID, defs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setSearchSubscription(paginationID: string, defaults: PaginatedSearchOptions) {
|
||||||
|
this.unsubscribeFromSearchOptions(paginationID);
|
||||||
|
const subs = [
|
||||||
|
this.subscribeToSearchOptions(defaults),
|
||||||
|
this.subscribeToPaginatedSearchOptions(paginationID || defaults.pagination.id, defaults)
|
||||||
|
];
|
||||||
|
this.subs.set(this.paginationID, subs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
||||||
* @param {SearchOptions} defaults Default values for when no parameters are available
|
* @param {SearchOptions} defaults Default values for when no parameters are available
|
||||||
@@ -283,14 +295,15 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
|
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
|
||||||
|
* @param {string} paginationId The pagination ID
|
||||||
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
|
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
|
||||||
* @returns {Subscription} The subscription to unsubscribe from
|
* @returns {Subscription} The subscription to unsubscribe from
|
||||||
*/
|
*/
|
||||||
private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription {
|
private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription {
|
||||||
return observableMerge(
|
return observableMerge(
|
||||||
|
this.getConfigurationPart(defaults.configuration),
|
||||||
this.getPaginationPart(paginationId, defaults.pagination),
|
this.getPaginationPart(paginationId, defaults.pagination),
|
||||||
this.getSortPart(paginationId, defaults.sort),
|
this.getSortPart(paginationId, defaults.sort),
|
||||||
this.getConfigurationPart(defaults.configuration),
|
|
||||||
this.getScopePart(defaults.scope),
|
this.getScopePart(defaults.scope),
|
||||||
this.getQueryPart(defaults.query),
|
this.getQueryPart(defaults.query),
|
||||||
this.getDSOTypePart(),
|
this.getDSOTypePart(),
|
||||||
@@ -304,30 +317,16 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default values for the Search Options
|
* Unsubscribe from all subscriptions related to the given paginationID
|
||||||
|
* @param paginationId The pagination id
|
||||||
*/
|
*/
|
||||||
get defaults(): Observable<RemoteData<PaginatedSearchOptions>> {
|
private unsubscribeFromSearchOptions(paginationId: string): void {
|
||||||
if (hasNoValue(this._defaults)) {
|
if (this.subs.has(this.paginationID)) {
|
||||||
const options = new PaginatedSearchOptions({
|
this.subs.get(this.paginationID)
|
||||||
pagination: this.defaultPagination,
|
.filter((sub) => hasValue(sub))
|
||||||
configuration: this.defaultConfiguration,
|
.forEach((sub) => sub.unsubscribe());
|
||||||
sort: this.defaultSort,
|
this.subs.delete(paginationId);
|
||||||
scope: this.defaultScope,
|
|
||||||
query: this.defaultQuery
|
|
||||||
});
|
|
||||||
this._defaults = createSuccessfulRemoteDataObject$(options, new Date().getTime());
|
|
||||||
}
|
}
|
||||||
return this._defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.forEach((sub) => {
|
|
||||||
sub.unsubscribe();
|
|
||||||
});
|
|
||||||
this.subs = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -10,8 +10,8 @@ import {
|
|||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
|
||||||
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
|
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
|
||||||
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||||
import { FilterType } from '../../../shared/search/filter-type.model';
|
import { FilterType } from '../../../shared/search/models/filter-type.model';
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user