mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 22:13:02 +00:00
Merge remote-tracking branch 'community/master' into submit-this-collection
# Conflicts: # src/app/submission/submission.service.ts
This commit is contained in:
15
.travis.yml
15
.travis.yml
@@ -1,5 +1,5 @@
|
|||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: bionic
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Install the latest docker-compose version for ci testing.
|
# Install the latest docker-compose version for ci testing.
|
||||||
@@ -12,6 +12,9 @@ env:
|
|||||||
DSPACE_REST_NAMESPACE: '/server/api'
|
DSPACE_REST_NAMESPACE: '/server/api'
|
||||||
DSPACE_REST_SSL: false
|
DSPACE_REST_SSL: false
|
||||||
|
|
||||||
|
services:
|
||||||
|
- xvfb
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
# Docker Compose Install
|
# Docker Compose Install
|
||||||
- curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
- curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||||
@@ -33,14 +36,6 @@ before_script:
|
|||||||
after_script:
|
after_script:
|
||||||
- docker-compose -f ./docker/docker-compose-travis.yml down
|
- docker-compose -f ./docker/docker-compose-travis.yml down
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- google-chrome
|
|
||||||
packages:
|
|
||||||
- dpkg
|
|
||||||
- google-chrome-stable
|
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
@@ -53,8 +48,6 @@ cache:
|
|||||||
bundler_args: --retry 5
|
bundler_args: --retry 5
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# Use Chromium instead of Chrome.
|
|
||||||
- export CHROME_BIN=chromium-browser
|
|
||||||
- yarn run build
|
- yarn run build
|
||||||
- yarn run ci
|
- yarn run ci
|
||||||
- cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
- cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||||
|
107
package.json
107
package.json
@@ -11,6 +11,7 @@
|
|||||||
"node": "8.* || >= 10.*"
|
"node": "8.* || >= 10.*"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"serialize-javascript": ">= 2.1.2",
|
||||||
"set-value": ">= 2.0.1"
|
"set-value": ">= 2.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -74,38 +75,38 @@
|
|||||||
"sync-i18n": "node ./scripts/sync-i18n-files.js"
|
"sync-i18n": "node ./scripts/sync-i18n-files.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^6.1.4",
|
"@angular/animations": "^7.2.15",
|
||||||
"@angular/cdk": "^6.4.7",
|
"@angular/cdk": "7.3.7",
|
||||||
"@angular/cli": "^6.1.5",
|
"@angular/cli": "^7.3.5",
|
||||||
"@angular/common": "^6.1.4",
|
"@angular/common": "^7.2.15",
|
||||||
"@angular/core": "^6.1.4",
|
"@angular/core": "^7.2.15",
|
||||||
"@angular/forms": "^6.1.4",
|
"@angular/forms": "^7.2.15",
|
||||||
"@angular/http": "^6.1.4",
|
"@angular/http": "^7.2.15",
|
||||||
"@angular/platform-browser": "^6.1.4",
|
"@angular/platform-browser": "^7.2.15",
|
||||||
"@angular/platform-browser-dynamic": "^6.1.4",
|
"@angular/platform-browser-dynamic": "^7.2.15",
|
||||||
"@angular/platform-server": "^6.1.4",
|
"@angular/platform-server": "^7.2.15",
|
||||||
"@angular/router": "^6.1.4",
|
"@angular/router": "^7.2.15",
|
||||||
"@angularclass/bootloader": "1.0.1",
|
"@angularclass/bootloader": "1.0.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^4.1.0",
|
||||||
"@ng-dynamic-forms/core": "6.2.0",
|
"@ng-dynamic-forms/core": "^7.1.0",
|
||||||
"@ng-dynamic-forms/ui-ng-bootstrap": "6.2.0",
|
"@ng-dynamic-forms/ui-ng-bootstrap": "^7.1.0",
|
||||||
"@ngrx/effects": "^6.1.0",
|
"@ngrx/effects": "^7.3.0",
|
||||||
"@ngrx/router-store": "^6.1.0",
|
"@ngrx/router-store": "^7.3.0",
|
||||||
"@ngrx/store": "^6.1.0",
|
"@ngrx/store": "^7.3.0",
|
||||||
"@nguniversal/express-engine": "6.1.0",
|
"@nguniversal/express-engine": "^7.1.1",
|
||||||
"@ngx-translate/core": "10.0.2",
|
"@ngx-translate/core": "11.0.1",
|
||||||
"@ngx-translate/http-loader": "3.0.1",
|
"@ngx-translate/http-loader": "4.0.0",
|
||||||
"@nicky-lenaers/ngx-scroll-to": "^1.0.0",
|
"@nicky-lenaers/ngx-scroll-to": "^1.0.0",
|
||||||
"angular-idle-preload": "3.0.0",
|
"angular-idle-preload": "3.0.0",
|
||||||
"angular-sortablejs": "^2.5.0",
|
"angular-sortablejs": "^2.5.0",
|
||||||
"angular2-text-mask": "9.0.0",
|
"angular2-text-mask": "9.0.0",
|
||||||
"angulartics2": "^6.2.0",
|
"angulartics2": "7.5.2",
|
||||||
"body-parser": "1.18.2",
|
"body-parser": "1.18.2",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"cerialize": "0.1.18",
|
"cerialize": "0.1.18",
|
||||||
"compression": "1.7.1",
|
"compression": "1.7.1",
|
||||||
"cookie-parser": "1.4.3",
|
"cookie-parser": "1.4.3",
|
||||||
"core-js": "^2.5.7",
|
"core-js": "^2.6.5",
|
||||||
"debug-loader": "^0.0.1",
|
"debug-loader": "^0.0.1",
|
||||||
"express": "4.16.2",
|
"express": "4.16.2",
|
||||||
"express-session": "1.15.6",
|
"express-session": "1.15.6",
|
||||||
@@ -113,6 +114,7 @@
|
|||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^0.4.10",
|
"fork-ts-checker-webpack-plugin": "^0.4.10",
|
||||||
|
"hammerjs": "^2.0.8",
|
||||||
"http-server": "0.11.1",
|
"http-server": "0.11.1",
|
||||||
"https": "1.0.0",
|
"https": "1.0.0",
|
||||||
"js-cookie": "2.2.0",
|
"js-cookie": "2.2.0",
|
||||||
@@ -122,18 +124,19 @@
|
|||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"methods": "1.1.2",
|
"methods": "1.1.2",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
|
"moment-range": "^4.0.2",
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"ng-mocks": "^6.2.1",
|
"ng-mocks": "^7.6.0",
|
||||||
"ng2-file-upload": "1.2.1",
|
"ng2-file-upload": "1.2.1",
|
||||||
"ng2-nouislider": "^1.7.11",
|
"ng2-nouislider": "^1.8.2",
|
||||||
"ngx-bootstrap": "^3.2.0",
|
"ngx-bootstrap": "^3.2.0",
|
||||||
"ngx-infinite-scroll": "6.0.1",
|
"ngx-infinite-scroll": "6.0.1",
|
||||||
"ngx-moment": "^3.1.0",
|
"ngx-moment": "^3.4.0",
|
||||||
"ngx-pagination": "3.0.3",
|
"ngx-pagination": "3.0.3",
|
||||||
"nouislider": "^11.0.0",
|
"nouislider": "^11.0.0",
|
||||||
"pem": "1.13.2",
|
"pem": "1.13.2",
|
||||||
"reflect-metadata": "0.1.12",
|
"reflect-metadata": "0.1.12",
|
||||||
"rxjs": "6.2.2",
|
"rxjs": "6.4.0",
|
||||||
"rxjs-spy": "^7.5.1",
|
"rxjs-spy": "^7.5.1",
|
||||||
"sass-resources-loader": "^2.0.0",
|
"sass-resources-loader": "^2.0.0",
|
||||||
"sortablejs": "1.7.0",
|
"sortablejs": "1.7.0",
|
||||||
@@ -143,17 +146,18 @@
|
|||||||
"url-parse": "^1.4.7",
|
"url-parse": "^1.4.7",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.2.0",
|
||||||
"zone.js": "^0.8.26"
|
"zone.js": "^0.8.29"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler": "^6.1.4",
|
"@angular-devkit/build-angular": "^0.13.5",
|
||||||
"@angular/compiler-cli": "^6.1.4",
|
"@angular/compiler": "^7.2.15",
|
||||||
|
"@angular/compiler-cli": "^7.2.15",
|
||||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||||
"@ngrx/entity": "^6.1.0",
|
"@ngrx/entity": "^7.3.0",
|
||||||
"@ngrx/schematics": "^6.1.0",
|
"@ngrx/schematics": "^7.3.0",
|
||||||
"@ngrx/store-devtools": "^6.1.0",
|
"@ngrx/store-devtools": "^7.3.0",
|
||||||
"@ngtools/webpack": "^6.1.5",
|
"@ngtools/webpack": "^7.3.9",
|
||||||
"@schematics/angular": "^0.7.5",
|
"@schematics/angular": "^0.7.5",
|
||||||
"@types/acorn": "^4.0.3",
|
"@types/acorn": "^4.0.3",
|
||||||
"@types/cookie-parser": "1.4.1",
|
"@types/cookie-parser": "1.4.1",
|
||||||
@@ -162,44 +166,47 @@
|
|||||||
"@types/express-serve-static-core": "4.16.0",
|
"@types/express-serve-static-core": "4.16.0",
|
||||||
"@types/file-saver": "^1.3.0",
|
"@types/file-saver": "^1.3.0",
|
||||||
"@types/hammerjs": "2.0.35",
|
"@types/hammerjs": "2.0.35",
|
||||||
"@types/jasmine": "^2.8.6",
|
"@types/jasmine": "^3.3.9",
|
||||||
"@types/js-cookie": "2.1.0",
|
"@types/js-cookie": "2.1.0",
|
||||||
"@types/json5": "^0.0.30",
|
"@types/json5": "^0.0.30",
|
||||||
"@types/lodash": "^4.14.110",
|
"@types/lodash": "^4.14.110",
|
||||||
"@types/memory-cache": "0.2.0",
|
"@types/memory-cache": "0.2.0",
|
||||||
"@types/mime": "2.0.0",
|
"@types/mime": "2.0.0",
|
||||||
"@types/node": "^10.9.4",
|
"@types/node": "^11.11.2",
|
||||||
"@types/serve-static": "1.13.2",
|
"@types/serve-static": "1.13.2",
|
||||||
"@types/uuid": "^3.4.3",
|
"@types/uuid": "^3.4.3",
|
||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.12.0",
|
||||||
|
"@typescript-eslint/parser": "^2.12.0",
|
||||||
"ajv": "^6.1.1",
|
"ajv": "^6.1.1",
|
||||||
"ajv-keywords": "^3.1.0",
|
"ajv-keywords": "^3.1.0",
|
||||||
"angular2-template-loader": "0.6.2",
|
"angular2-template-loader": "0.6.2",
|
||||||
"autoprefixer": "^9.1.3",
|
"autoprefixer": "^9.1.3",
|
||||||
"caniuse-lite": "^1.0.30000697",
|
"caniuse-lite": "^1.0.30000697",
|
||||||
"cli-progress": "^3.3.1",
|
"cli-progress": "^3.3.1",
|
||||||
"codelyzer": "^4.4.4",
|
"codelyzer": "^5.1.0",
|
||||||
"commander": "^3.0.2",
|
"commander": "^3.0.2",
|
||||||
"compression-webpack-plugin": "^1.1.6",
|
"compression-webpack-plugin": "^3.0.1",
|
||||||
"copy-webpack-plugin": "^4.4.1",
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
"coveralls": "3.0.0",
|
"coveralls": "3.0.0",
|
||||||
"css-loader": "1.0.0",
|
"css-loader": "3.4.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"html-webpack-plugin": "^4.0.0-alpha",
|
"html-webpack-plugin": "3.2.0",
|
||||||
"imports-loader": "0.8.0",
|
"imports-loader": "0.8.0",
|
||||||
"istanbul-instrumenter-loader": "3.0.1",
|
"istanbul-instrumenter-loader": "3.0.1",
|
||||||
"jasmine-core": "^3.2.1",
|
"jasmine-core": "^3.3.0",
|
||||||
"jasmine-marbles": "0.3.1",
|
"jasmine-marbles": "0.3.1",
|
||||||
"jasmine-spec-reporter": "4.2.1",
|
"jasmine-spec-reporter": "4.2.1",
|
||||||
"karma": "3.0.0",
|
"karma": "4.0.1",
|
||||||
"karma-chrome-launcher": "2.2.0",
|
"karma-chrome-launcher": "2.2.0",
|
||||||
"karma-cli": "1.0.1",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "1.1.2",
|
"karma-coverage": "1.1.2",
|
||||||
"karma-istanbul-preprocessor": "0.0.2",
|
"karma-istanbul-preprocessor": "0.0.2",
|
||||||
"karma-jasmine": "1.1.2",
|
"karma-jasmine": "2.0.1",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"karma-phantomjs-launcher": "1.0.4",
|
"karma-phantomjs-launcher": "1.0.4",
|
||||||
"karma-remap-coverage": "^0.1.5",
|
"karma-remap-coverage": "^0.1.5",
|
||||||
@@ -223,26 +230,26 @@
|
|||||||
"protractor": "^5.4.2",
|
"protractor": "^5.4.2",
|
||||||
"protractor-istanbul-plugin": "2.0.0",
|
"protractor-istanbul-plugin": "2.0.0",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"resolve-url-loader": "^2.3.0",
|
|
||||||
"rimraf": "2.6.2",
|
"rimraf": "2.6.2",
|
||||||
"rollup": "^0.65.0",
|
"rollup": "^0.65.0",
|
||||||
"rollup-plugin-commonjs": "^9.1.6",
|
"rollup-plugin-commonjs": "^9.1.6",
|
||||||
"rollup-plugin-node-globals": "1.2.1",
|
"rollup-plugin-node-globals": "1.2.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.3",
|
"rollup-plugin-node-resolve": "^3.0.3",
|
||||||
"rollup-plugin-terser": "^2.0.2",
|
"rollup-plugin-terser": "^2.0.2",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "7.3.1",
|
||||||
"script-ext-html-webpack-plugin": "2.0.1",
|
"script-ext-html-webpack-plugin": "2.1.4",
|
||||||
"source-map": "0.7.3",
|
"source-map": "0.7.3",
|
||||||
"source-map-loader": "0.2.4",
|
"source-map-loader": "0.2.4",
|
||||||
"string-replace-loader": "^2.1.1",
|
"string-replace-loader": "^2.1.1",
|
||||||
|
"terser-webpack-plugin": "^2.3.1",
|
||||||
"to-string-loader": "1.1.5",
|
"to-string-loader": "1.1.5",
|
||||||
"ts-helpers": "1.1.2",
|
"ts-helpers": "1.1.2",
|
||||||
"ts-node": "4.1.0",
|
"ts-node": "4.1.0",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"typedoc": "^0.9.0",
|
"typedoc": "^0.9.0",
|
||||||
"typescript": "^2.9.1",
|
"typescript": "3.1.6",
|
||||||
"webdriver-manager": "^12.1.7",
|
"webdriver-manager": "^12.1.7",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.29.6",
|
||||||
"webpack-bundle-analyzer": "^3.3.2",
|
"webpack-bundle-analyzer": "^3.3.2",
|
||||||
"webpack-dev-middleware": "3.2.0",
|
"webpack-dev-middleware": "3.2.0",
|
||||||
"webpack-dev-server": "^3.1.11",
|
"webpack-dev-server": "^3.1.11",
|
||||||
|
@@ -1566,6 +1566,8 @@
|
|||||||
|
|
||||||
"search.results.no-results-link": "quotes around it",
|
"search.results.no-results-link": "quotes around it",
|
||||||
|
|
||||||
|
"search.results.empty": "Your search returned no results.",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"search.sidebar.close": "Back to results",
|
"search.sidebar.close": "Back to results",
|
||||||
@@ -1639,13 +1641,21 @@
|
|||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items",
|
"submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Author": "Search for Authors",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Author": "Local Authors ({{ count }})",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Search for Journals",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Local Journals ({{ count }})",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Issue": "Search for Journal Issues",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Issue": "Local Journal Issues ({{ count }})",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Local Journal Volumes ({{ count }})",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.orcidV2": "ORCID ({{ count }})",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.lcname": "LC Names ({{ count }})",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies",
|
||||||
|
|
||||||
@@ -1679,6 +1689,14 @@
|
|||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Issue": "Selected Issue",
|
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Issue": "Selected Issue",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.selection-tab.title.sherpaJournal": "Search Results",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.selection-tab.title.sherpaPublisher": "Search Results",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.selection-tab.title.orcidV2": "Search Results",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.",
|
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
|
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { MetadataRegistryComponent } from './metadata-registry.component';
|
import { MetadataRegistryComponent } from './metadata-registry.component';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
@@ -18,6 +17,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
|||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
|
|
||||||
describe('MetadataRegistryComponent', () => {
|
describe('MetadataRegistryComponent', () => {
|
||||||
let comp: MetadataRegistryComponent;
|
let comp: MetadataRegistryComponent;
|
||||||
@@ -101,12 +101,12 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
|
|
||||||
it('should start editing the selected schema', async(() => {
|
it('should start editing the selected schema', async(() => {
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
expect(registryService.editMetadataSchema).toHaveBeenCalledWith(mockSchemasList[0]);
|
expect(registryService.editMetadataSchema).toHaveBeenCalledWith(mockSchemasList[0] as MetadataSchema);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected schema when clicked again', async(() => {
|
it('should cancel editing the selected schema when clicked again', async(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0]));
|
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0] as MetadataSchema));
|
||||||
spyOn(registryService, 'cancelEditMetadataSchema');
|
spyOn(registryService, 'cancelEditMetadataSchema');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -121,7 +121,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
||||||
spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas));
|
spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas as MetadataSchema[]));
|
||||||
comp.deleteSchemas();
|
comp.deleteSchemas();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { MetadataSchemaComponent } from './metadata-schema.component';
|
import { MetadataSchemaComponent } from './metadata-schema.component';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -22,6 +21,7 @@ import { NotificationsServiceStub } from '../../../shared/testing/notifications-
|
|||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
|
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||||
|
|
||||||
describe('MetadataSchemaComponent', () => {
|
describe('MetadataSchemaComponent', () => {
|
||||||
let comp: MetadataSchemaComponent;
|
let comp: MetadataSchemaComponent;
|
||||||
@@ -152,12 +152,12 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
|
|
||||||
it('should start editing the selected field', async(() => {
|
it('should start editing the selected field', async(() => {
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
expect(registryService.editMetadataField).toHaveBeenCalledWith(mockFieldsList[2]);
|
expect(registryService.editMetadataField).toHaveBeenCalledWith(mockFieldsList[2] as MetadataField);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected field when clicked again', async(() => {
|
it('should cancel editing the selected field when clicked again', async(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2]));
|
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2] as MetadataField));
|
||||||
spyOn(registryService, 'cancelEditMetadataField');
|
spyOn(registryService, 'cancelEditMetadataField');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -172,7 +172,7 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
||||||
spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields));
|
spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields as MetadataField[]));
|
||||||
comp.deleteFields();
|
comp.deleteFields();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { DynamicFormService, DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
|
import {
|
||||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
DynamicFormControlModel,
|
||||||
|
DynamicFormService,
|
||||||
|
DynamicInputModel,
|
||||||
|
DynamicTextAreaModel
|
||||||
|
} 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-forms/comcol-form/comcol-form.component';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { DynamicFormService, DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
|
import {
|
||||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
DynamicFormControlModel,
|
||||||
|
DynamicFormService,
|
||||||
|
DynamicInputModel,
|
||||||
|
DynamicTextAreaModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ResourceType } from '../../core/shared/resource-type';
|
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
||||||
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
||||||
<ul>
|
<ds-viewable-collection
|
||||||
<li *ngFor="let collection of subCollectionsRD?.payload.page">
|
[config]="config"
|
||||||
<p>
|
[sortConfig]="sortConfig"
|
||||||
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
|
[objects]="subCollectionsRD"
|
||||||
<span class="text-muted">{{collection.shortDescription}}</span>
|
[hideGear]="false"
|
||||||
</p>
|
(paginationChange)="onPaginationChange($event)">
|
||||||
</li>
|
</ds-viewable-collection>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="subCollectionsRD?.hasFailed" message="{{'error.sub-collections' | translate}}"></ds-error>
|
<ds-error *ngIf="subCollectionsRD?.hasFailed" message="{{'error.sub-collections' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="subCollectionsRD?.isLoading" message="{{'loading.sub-collections' | translate}}"></ds-loading>
|
<ds-loading *ngIf="subCollectionsRD?.isLoading" message="{{'loading.sub-collections' | translate}}"></ds-loading>
|
||||||
|
@@ -0,0 +1,182 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||||
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
|
import { FindListOptions } from '../../core/data/request.models';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||||
|
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
|
|
||||||
|
describe('CommunityPageSubCollectionList Component', () => {
|
||||||
|
let comp: CommunityPageSubCollectionListComponent;
|
||||||
|
let fixture: ComponentFixture<CommunityPageSubCollectionListComponent>;
|
||||||
|
let collectionDataServiceStub: any;
|
||||||
|
let subCollList = [];
|
||||||
|
|
||||||
|
const collections = [Object.assign(new Community(), {
|
||||||
|
id: '123456789-1',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 1' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-2',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 2' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-3',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 3' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-4',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 4' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-5',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 5' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-6',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 6' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-7',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Collection 7' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockCommunity = Object.assign(new Community(), {
|
||||||
|
id: '123456789',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'Test title' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
collectionDataServiceStub = {
|
||||||
|
findByParent(parentUUID: string, options: FindListOptions = {}) {
|
||||||
|
let currentPage = options.currentPage;
|
||||||
|
let elementsPerPage = options.elementsPerPage;
|
||||||
|
if (currentPage === undefined) {
|
||||||
|
currentPage = 1
|
||||||
|
}
|
||||||
|
elementsPerPage = 5;
|
||||||
|
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||||
|
let endPageIndex = (currentPage * elementsPerPage);
|
||||||
|
if (endPageIndex > subCollList.length) {
|
||||||
|
endPageIndex = subCollList.length;
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCollList.slice(startPageIndex, endPageIndex)));
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
SharedModule,
|
||||||
|
RouterTestingModule.withRoutes([]),
|
||||||
|
NgbModule.forRoot(),
|
||||||
|
NoopAnimationsModule
|
||||||
|
],
|
||||||
|
declarations: [CommunityPageSubCollectionListComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
|
{ provide: SelectableListService, useValue: {} },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityPageSubCollectionListComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.community = mockCommunity;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a list of collections', () => {
|
||||||
|
subCollList = collections;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
|
expect(collList.length).toEqual(5);
|
||||||
|
expect(collList[0].nativeElement.textContent).toContain('Collection 1');
|
||||||
|
expect(collList[1].nativeElement.textContent).toContain('Collection 2');
|
||||||
|
expect(collList[2].nativeElement.textContent).toContain('Collection 3');
|
||||||
|
expect(collList[3].nativeElement.textContent).toContain('Collection 4');
|
||||||
|
expect(collList[4].nativeElement.textContent).toContain('Collection 5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display the header when list of collections is empty', () => {
|
||||||
|
subCollList = [];
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const subComHead = fixture.debugElement.queryAll(By.css('h2'));
|
||||||
|
expect(subComHead.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list of collections on pagination change', () => {
|
||||||
|
subCollList = collections;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const pagination = Object.create({
|
||||||
|
pagination:{
|
||||||
|
id: comp.pageId,
|
||||||
|
currentPage: 2,
|
||||||
|
pageSize: 5
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
field: 'dc.title',
|
||||||
|
direction: 'ASC'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
comp.onPaginationChange(pagination);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
|
expect(collList.length).toEqual(2);
|
||||||
|
expect(collList[0].nativeElement.textContent).toContain('Collection 6');
|
||||||
|
expect(collList[1].nativeElement.textContent).toContain('Collection 7');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,12 +1,16 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { fadeIn } from '../../shared/animations/fade';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-collection-list',
|
selector: 'ds-community-page-sub-collection-list',
|
||||||
@@ -16,9 +20,60 @@ import { PaginatedList } from '../../core/data/paginated-list';
|
|||||||
})
|
})
|
||||||
export class CommunityPageSubCollectionListComponent implements OnInit {
|
export class CommunityPageSubCollectionListComponent implements OnInit {
|
||||||
@Input() community: Community;
|
@Input() community: Community;
|
||||||
subCollectionsRDObs: Observable<RemoteData<PaginatedList<Collection>>>;
|
|
||||||
|
/**
|
||||||
|
* The pagination configuration
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pagination id
|
||||||
|
*/
|
||||||
|
pageId = 'community-collections-pagination';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sorting configuration
|
||||||
|
*/
|
||||||
|
sortConfig: SortOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of remote data objects of communities' collections
|
||||||
|
*/
|
||||||
|
subCollectionsRDObs: BehaviorSubject<RemoteData<PaginatedList<Collection>>> = new BehaviorSubject<RemoteData<PaginatedList<Collection>>>({} as any);
|
||||||
|
|
||||||
|
constructor(private cds: CollectionDataService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.subCollectionsRDObs = this.community.collections;
|
this.config = new PaginationComponentOptions();
|
||||||
|
this.config.id = this.pageId;
|
||||||
|
this.config.pageSize = 5;
|
||||||
|
this.config.currentPage = 1;
|
||||||
|
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
|
this.updatePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when one of the pagination settings is changed
|
||||||
|
* @param event The new pagination data
|
||||||
|
*/
|
||||||
|
onPaginationChange(event) {
|
||||||
|
this.config.currentPage = event.pagination.currentPage;
|
||||||
|
this.config.pageSize = event.pagination.pageSize;
|
||||||
|
this.sortConfig.field = event.sort.field;
|
||||||
|
this.sortConfig.direction = event.sort.direction;
|
||||||
|
this.updatePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of collections
|
||||||
|
*/
|
||||||
|
updatePage() {
|
||||||
|
this.cds.findByParent(this.community.id,{
|
||||||
|
currentPage: this.config.currentPage,
|
||||||
|
elementsPerPage: this.config.pageSize,
|
||||||
|
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
|
||||||
|
}).pipe(take(1)).subscribe((results) => {
|
||||||
|
this.subCollectionsRDObs.next(results);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
||||||
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-community-list.head' | translate}}</h2>
|
<h2>{{'community.sub-community-list.head' | translate}}</h2>
|
||||||
<ul>
|
<ds-viewable-collection
|
||||||
<li *ngFor="let community of subCommunitiesRD?.payload.page">
|
[config]="config"
|
||||||
<p>
|
[sortConfig]="sortConfig"
|
||||||
<span class="lead"><a [routerLink]="['/communities', community.id]">{{community.name}}</a></span><br>
|
[objects]="subCommunitiesRD"
|
||||||
<span class="text-muted">{{community.shortDescription}}</span>
|
[hideGear]="false"
|
||||||
</p>
|
(paginationChange)="onPaginationChange($event)">
|
||||||
</li>
|
</ds-viewable-collection>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="subCommunitiesRD?.hasFailed" message="{{'error.sub-communities' | translate}}"></ds-error>
|
<ds-error *ngIf="subCommunitiesRD?.hasFailed" message="{{'error.sub-communities' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="subCommunitiesRD?.isLoading" message="{{'loading.sub-communities' | translate}}"></ds-loading>
|
<ds-loading *ngIf="subCommunitiesRD?.isLoading" message="{{'loading.sub-communities' | translate}}"></ds-loading>
|
||||||
|
@@ -1,21 +1,29 @@
|
|||||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {NO_ERRORS_SCHEMA} from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import {CommunityPageSubCommunityListComponent} from './community-page-sub-community-list.component';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import {Community} from '../../core/shared/community.model';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import {RemoteData} from '../../core/data/remote-data';
|
import { By } from '@angular/platform-browser';
|
||||||
import {PaginatedList} from '../../core/data/paginated-list';
|
|
||||||
import {PageInfo} from '../../core/shared/page-info.model';
|
|
||||||
import {SharedModule} from '../../shared/shared.module';
|
|
||||||
import {RouterTestingModule} from '@angular/router/testing';
|
|
||||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
|
||||||
import {By} from '@angular/platform-browser';
|
|
||||||
import {of as observableOf, Observable } from 'rxjs';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
|
||||||
|
|
||||||
describe('SubCommunityList Component', () => {
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||||
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { FindListOptions } from '../../core/data/request.models';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||||
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
|
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
|
|
||||||
|
describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||||
let comp: CommunityPageSubCommunityListComponent;
|
let comp: CommunityPageSubCommunityListComponent;
|
||||||
let fixture: ComponentFixture<CommunityPageSubCommunityListComponent>;
|
let fixture: ComponentFixture<CommunityPageSubCommunityListComponent>;
|
||||||
|
let communityDataServiceStub: any;
|
||||||
|
let subCommList = [];
|
||||||
|
|
||||||
const subcommunities = [Object.assign(new Community(), {
|
const subcommunities = [Object.assign(new Community(), {
|
||||||
id: '123456789-1',
|
id: '123456789-1',
|
||||||
@@ -32,34 +40,92 @@ describe('SubCommunityList Component', () => {
|
|||||||
{ language: 'en_US', value: 'SubCommunity 2' }
|
{ language: 'en_US', value: 'SubCommunity 2' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-3',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'SubCommunity 3' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '12345678942',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'SubCommunity 4' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-5',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'SubCommunity 5' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-6',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'SubCommunity 6' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-7',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'SubCommunity 7' }
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
const emptySubCommunitiesCommunity = Object.assign(new Community(), {
|
const mockCommunity = Object.assign(new Community(), {
|
||||||
|
id: '123456789',
|
||||||
metadata: {
|
metadata: {
|
||||||
'dc.title': [
|
'dc.title': [
|
||||||
{ language: 'en_US', value: 'Test title' }
|
{ language: 'en_US', value: 'Test title' }
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockCommunity = Object.assign(new Community(), {
|
communityDataServiceStub = {
|
||||||
metadata: {
|
findByParent(parentUUID: string, options: FindListOptions = {}) {
|
||||||
'dc.title': [
|
let currentPage = options.currentPage;
|
||||||
{ language: 'en_US', value: 'Test title' }
|
let elementsPerPage = options.elementsPerPage;
|
||||||
]
|
if (currentPage === undefined) {
|
||||||
},
|
currentPage = 1
|
||||||
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subcommunities))
|
}
|
||||||
})
|
elementsPerPage = 5;
|
||||||
;
|
|
||||||
|
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||||
|
let endPageIndex = (currentPage * elementsPerPage);
|
||||||
|
if (endPageIndex > subCommList.length) {
|
||||||
|
endPageIndex = subCommList.length;
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCommList.slice(startPageIndex, endPageIndex)));
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), SharedModule,
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
SharedModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
NoopAnimationsModule],
|
NgbModule.forRoot(),
|
||||||
|
NoopAnimationsModule
|
||||||
|
],
|
||||||
declarations: [CommunityPageSubCommunityListComponent],
|
declarations: [CommunityPageSubCommunityListComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
|
{ provide: SelectableListService, useValue: {} },
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -67,23 +133,52 @@ describe('SubCommunityList Component', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(CommunityPageSubCommunityListComponent);
|
fixture = TestBed.createComponent(CommunityPageSubCommunityListComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
comp.community = mockCommunity;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display a list of subCommunities', () => {
|
it('should display a list of sub-communities', () => {
|
||||||
comp.community = mockCommunity;
|
subCommList = subcommunities;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const subComList = fixture.debugElement.queryAll(By.css('li'));
|
const subComList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
expect(subComList.length).toEqual(2);
|
expect(subComList.length).toEqual(5);
|
||||||
expect(subComList[0].nativeElement.textContent).toContain('SubCommunity 1');
|
expect(subComList[0].nativeElement.textContent).toContain('SubCommunity 1');
|
||||||
expect(subComList[1].nativeElement.textContent).toContain('SubCommunity 2');
|
expect(subComList[1].nativeElement.textContent).toContain('SubCommunity 2');
|
||||||
|
expect(subComList[2].nativeElement.textContent).toContain('SubCommunity 3');
|
||||||
|
expect(subComList[3].nativeElement.textContent).toContain('SubCommunity 4');
|
||||||
|
expect(subComList[4].nativeElement.textContent).toContain('SubCommunity 5');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not display the header when subCommunities are empty', () => {
|
it('should not display the header when list of sub-communities is empty', () => {
|
||||||
comp.community = emptySubCommunitiesCommunity;
|
subCommList = [];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const subComHead = fixture.debugElement.queryAll(By.css('h2'));
|
const subComHead = fixture.debugElement.queryAll(By.css('h2'));
|
||||||
expect(subComHead.length).toEqual(0);
|
expect(subComHead.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update list of sub-communities on pagination change', () => {
|
||||||
|
subCommList = subcommunities;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const pagination = Object.create({
|
||||||
|
pagination:{
|
||||||
|
id: comp.pageId,
|
||||||
|
currentPage: 2,
|
||||||
|
pageSize: 5
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
field: 'dc.title',
|
||||||
|
direction: 'ASC'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
comp.onPaginationChange(pagination);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
|
expect(collList.length).toEqual(2);
|
||||||
|
expect(collList[0].nativeElement.textContent).toContain('SubCommunity 6');
|
||||||
|
expect(collList[1].nativeElement.textContent).toContain('SubCommunity 7');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,26 +1,82 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { fadeIn } from '../../shared/animations/fade';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
import {Observable} from 'rxjs';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-community-list',
|
selector: 'ds-community-page-sub-community-list',
|
||||||
styleUrls: ['./community-page-sub-community-list.component.scss'],
|
styleUrls: ['./community-page-sub-community-list.component.scss'],
|
||||||
templateUrl: './community-page-sub-community-list.component.html',
|
templateUrl: './community-page-sub-community-list.component.html',
|
||||||
animations:[fadeIn]
|
animations: [fadeIn]
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component to render the sub-communities of a Community
|
* Component to render the sub-communities of a Community
|
||||||
*/
|
*/
|
||||||
export class CommunityPageSubCommunityListComponent implements OnInit {
|
export class CommunityPageSubCommunityListComponent implements OnInit {
|
||||||
@Input() community: Community;
|
@Input() community: Community;
|
||||||
subCommunitiesRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
|
||||||
|
/**
|
||||||
|
* The pagination configuration
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pagination id
|
||||||
|
*/
|
||||||
|
pageId = 'community-subCommunities-pagination';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sorting configuration
|
||||||
|
*/
|
||||||
|
sortConfig: SortOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of remote data objects of communities' collections
|
||||||
|
*/
|
||||||
|
subCommunitiesRDObs: BehaviorSubject<RemoteData<PaginatedList<Community>>> = new BehaviorSubject<RemoteData<PaginatedList<Community>>>({} as any);
|
||||||
|
|
||||||
|
constructor(private cds: CommunityDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.subCommunitiesRDObs = this.community.subcommunities;
|
this.config = new PaginationComponentOptions();
|
||||||
|
this.config.id = this.pageId;
|
||||||
|
this.config.pageSize = 5;
|
||||||
|
this.config.currentPage = 1;
|
||||||
|
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
|
this.updatePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when one of the pagination settings is changed
|
||||||
|
* @param event The new pagination data
|
||||||
|
*/
|
||||||
|
onPaginationChange(event) {
|
||||||
|
this.config.currentPage = event.pagination.currentPage;
|
||||||
|
this.config.pageSize = event.pagination.pageSize;
|
||||||
|
this.sortConfig.field = event.sort.field;
|
||||||
|
this.sortConfig.direction = event.sort.direction;
|
||||||
|
this.updatePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of sub-communities
|
||||||
|
*/
|
||||||
|
updatePage() {
|
||||||
|
this.cds.findByParent(this.community.id, {
|
||||||
|
currentPage: this.config.currentPage,
|
||||||
|
elementsPerPage: this.config.pageSize,
|
||||||
|
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
|
||||||
|
}).pipe(take(1)).subscribe((results) => {
|
||||||
|
this.subCommunitiesRDObs.next(results);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,161 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { TopLevelCommunityListComponent } from './top-level-community-list.component';
|
||||||
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { FindListOptions } from '../../core/data/request.models';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||||
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
|
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
|
|
||||||
|
describe('TopLevelCommunityList Component', () => {
|
||||||
|
let comp: TopLevelCommunityListComponent;
|
||||||
|
let fixture: ComponentFixture<TopLevelCommunityListComponent>;
|
||||||
|
let communityDataServiceStub: any;
|
||||||
|
|
||||||
|
const topCommList = [Object.assign(new Community(), {
|
||||||
|
id: '123456789-1',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 1' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-2',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 2' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-3',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 3' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '12345678942',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 4' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-5',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 5' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-6',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 6' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: '123456789-7',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{ language: 'en_US', value: 'TopCommunity 7' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
communityDataServiceStub = {
|
||||||
|
findTop(options: FindListOptions = {}) {
|
||||||
|
let currentPage = options.currentPage;
|
||||||
|
let elementsPerPage = options.elementsPerPage;
|
||||||
|
if (currentPage === undefined) {
|
||||||
|
currentPage = 1
|
||||||
|
}
|
||||||
|
elementsPerPage = 5;
|
||||||
|
|
||||||
|
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||||
|
let endPageIndex = (currentPage * elementsPerPage);
|
||||||
|
if (endPageIndex > topCommList.length) {
|
||||||
|
endPageIndex = topCommList.length;
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), topCommList.slice(startPageIndex, endPageIndex)));
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
SharedModule,
|
||||||
|
RouterTestingModule.withRoutes([]),
|
||||||
|
NgbModule.forRoot(),
|
||||||
|
NoopAnimationsModule
|
||||||
|
],
|
||||||
|
declarations: [TopLevelCommunityListComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
|
{ provide: SelectableListService, useValue: {} },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TopLevelCommunityListComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a list of top-communities', () => {
|
||||||
|
const subComList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
|
|
||||||
|
expect(subComList.length).toEqual(5);
|
||||||
|
expect(subComList[0].nativeElement.textContent).toContain('TopCommunity 1');
|
||||||
|
expect(subComList[1].nativeElement.textContent).toContain('TopCommunity 2');
|
||||||
|
expect(subComList[2].nativeElement.textContent).toContain('TopCommunity 3');
|
||||||
|
expect(subComList[3].nativeElement.textContent).toContain('TopCommunity 4');
|
||||||
|
expect(subComList[4].nativeElement.textContent).toContain('TopCommunity 5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list of top-communities on pagination change', () => {
|
||||||
|
const pagination = Object.create({
|
||||||
|
pagination:{
|
||||||
|
id: comp.pageId,
|
||||||
|
currentPage: 2,
|
||||||
|
pageSize: 5
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
field: 'dc.title',
|
||||||
|
direction: 'ASC'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
comp.onPaginationChange(pagination);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||||
|
expect(collList.length).toEqual(2);
|
||||||
|
expect(collList[0].nativeElement.textContent).toContain('TopCommunity 6');
|
||||||
|
expect(collList[1].nativeElement.textContent).toContain('TopCommunity 7');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,15 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
|
||||||
import { fadeInOut } from '../../shared/animations/fade';
|
import { fadeInOut } from '../../shared/animations/fade';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this component renders the Top-Level Community list
|
* this component renders the Top-Level Community list
|
||||||
@@ -33,6 +33,11 @@ export class TopLevelCommunityListComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
config: PaginationComponentOptions;
|
config: PaginationComponentOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pagination id
|
||||||
|
*/
|
||||||
|
pageId = 'top-level-pagination';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sorting configuration
|
* The sorting configuration
|
||||||
*/
|
*/
|
||||||
@@ -40,7 +45,7 @@ export class TopLevelCommunityListComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(private cds: CommunityDataService) {
|
constructor(private cds: CommunityDataService) {
|
||||||
this.config = new PaginationComponentOptions();
|
this.config = new PaginationComponentOptions();
|
||||||
this.config.id = 'top-level-pagination';
|
this.config.id = this.pageId;
|
||||||
this.config.pageSize = 5;
|
this.config.pageSize = 5;
|
||||||
this.config.currentPage = 1;
|
this.config.currentPage = 1;
|
||||||
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
@@ -55,10 +60,10 @@ export class TopLevelCommunityListComponent implements OnInit {
|
|||||||
* @param event The new pagination data
|
* @param event The new pagination data
|
||||||
*/
|
*/
|
||||||
onPaginationChange(event) {
|
onPaginationChange(event) {
|
||||||
this.config.currentPage = event.page;
|
this.config.currentPage = event.pagination.currentPage;
|
||||||
this.config.pageSize = event.pageSize;
|
this.config.pageSize = event.pagination.pageSize;
|
||||||
this.sortConfig.field = event.sortField;
|
this.sortConfig.field = event.sort.field;
|
||||||
this.sortConfig.direction = event.sortDirection;
|
this.sortConfig.direction = event.sort.direction;
|
||||||
this.updatePage();
|
this.updatePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,10 +5,10 @@ import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angu
|
|||||||
import { pushInOut } from '../shared/animations/push';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
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 { Router } from '@angular/router';
|
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a search page using a configuration as input.
|
* This component renders a search page using a configuration as input.
|
||||||
@@ -61,5 +61,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements
|
|||||||
if (hasValue(this.configuration)) {
|
if (hasValue(this.configuration)) {
|
||||||
this.routeService.setParameter('configuration', this.configuration);
|
this.routeService.setParameter('configuration', this.configuration);
|
||||||
}
|
}
|
||||||
|
if (hasValue(this.fixedFilterQuery)) {
|
||||||
|
this.routeService.setParameter('fixedFilter', this.fixedFilterQuery);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,18 +3,28 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CoreModule } from '../core/core.module';
|
import { CoreModule } from '../core/core.module';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { SearchPageRoutingModule } from './search-page-routing.module';
|
import { SearchPageRoutingModule } from './search-page-routing.module';
|
||||||
import { SearchPageComponent } from './search-page.component';
|
import { SearchComponent } from './search.component';
|
||||||
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
|
import { SidebarEffects } from '../shared/sidebar/sidebar-effects.service';
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { SearchTrackerComponent } from './search-tracker.component';
|
import { SearchPageComponent } from './search-page.component';
|
||||||
|
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { SearchComponent } from './search.component';
|
import { SearchTrackerComponent } from './search-tracker.component';
|
||||||
|
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||||
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
|
|
||||||
|
const effects = [
|
||||||
|
SidebarEffects
|
||||||
|
];
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
SearchPageComponent,
|
SearchPageComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
ConfigurationSearchPageComponent,
|
ConfigurationSearchPageComponent,
|
||||||
SearchTrackerComponent
|
SearchTrackerComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -22,11 +32,18 @@ const components = [
|
|||||||
SearchPageRoutingModule,
|
SearchPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
EffectsModule.forFeature(effects),
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
],
|
],
|
||||||
providers: [ConfigurationSearchPageGuard],
|
|
||||||
declarations: components,
|
declarations: components,
|
||||||
|
providers: [
|
||||||
|
SidebarService,
|
||||||
|
SidebarFilterService,
|
||||||
|
SearchFilterService,
|
||||||
|
ConfigurationSearchPageGuard,
|
||||||
|
SearchConfigurationService
|
||||||
|
],
|
||||||
exports: components
|
exports: components
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -82,6 +82,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
angulartics2GoogleAnalytics.startTracking();
|
||||||
angulartics2DSpace.startTracking();
|
angulartics2DSpace.startTracking();
|
||||||
|
|
||||||
metadata.listenForRouteChange();
|
metadata.listenForRouteChange();
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||||
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
||||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||||
@@ -8,23 +9,28 @@ import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filt
|
|||||||
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
||||||
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
||||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||||
import { metadataRegistryReducer, MetadataRegistryState } from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
import {
|
||||||
|
metadataRegistryReducer,
|
||||||
|
MetadataRegistryState
|
||||||
|
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
||||||
import { hasValue } from './shared/empty.util';
|
import { hasValue } from './shared/empty.util';
|
||||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||||
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
||||||
import { historyReducer, HistoryState } from './shared/history/history.reducer';
|
import {
|
||||||
import { selectableListReducer, SelectableListsState } from './shared/object-list/selectable-list/selectable-list.reducer';
|
selectableListReducer,
|
||||||
import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
SelectableListsState
|
||||||
|
} from './shared/object-list/selectable-list/selectable-list.reducer';
|
||||||
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
||||||
import { NameVariantListsState, nameVariantReducer } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
import {
|
||||||
|
NameVariantListsState,
|
||||||
|
nameVariantReducer
|
||||||
|
} from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
history: HistoryState;
|
|
||||||
hostWindow: HostWindowState;
|
hostWindow: HostWindowState;
|
||||||
forms: FormState;
|
forms: FormState;
|
||||||
metadataRegistry: MetadataRegistryState;
|
metadataRegistry: MetadataRegistryState;
|
||||||
bitstreamFormats: BitstreamFormatRegistryState;
|
|
||||||
notifications: NotificationsState;
|
notifications: NotificationsState;
|
||||||
sidebar: SidebarState;
|
sidebar: SidebarState;
|
||||||
sidebarFilter: SidebarFiltersState;
|
sidebarFilter: SidebarFiltersState;
|
||||||
@@ -40,11 +46,9 @@ export interface AppState {
|
|||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
router: fromRouter.routerReducer,
|
router: fromRouter.routerReducer,
|
||||||
history: historyReducer,
|
|
||||||
hostWindow: hostWindowReducer,
|
hostWindow: hostWindowReducer,
|
||||||
forms: formReducer,
|
forms: formReducer,
|
||||||
metadataRegistry: metadataRegistryReducer,
|
metadataRegistry: metadataRegistryReducer,
|
||||||
bitstreamFormats: bitstreamFormatReducer,
|
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
sidebar: sidebarReducer,
|
sidebar: sidebarReducer,
|
||||||
sidebarFilter: sidebarFilterReducer,
|
sidebarFilter: sidebarFilterReducer,
|
||||||
|
@@ -312,7 +312,7 @@ export class CommunityListService {
|
|||||||
|
|
||||||
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||||
.pipe(
|
.pipe(
|
||||||
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
|
filter((rd: RemoteData<PaginatedList<Collection>>) => rd.hasSucceeded),
|
||||||
take(1),
|
take(1),
|
||||||
map((results) => results.payload.totalElements > 0),
|
map((results) => results.payload.totalElements > 0),
|
||||||
);
|
);
|
||||||
@@ -320,8 +320,8 @@ export class CommunityListService {
|
|||||||
let hasChildren$: Observable<boolean>;
|
let hasChildren$: Observable<boolean>;
|
||||||
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
|
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
|
||||||
take(1),
|
take(1),
|
||||||
map((result: [boolean]) => {
|
map(([hasSubcoms, hasColls]: [boolean, boolean]) => {
|
||||||
if (result[0] || result[1]) {
|
if (hasSubcoms || hasColls) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -60,7 +60,8 @@ export class AuthService {
|
|||||||
// and is not the login route, clear redirect url and messages
|
// and is not the login route, clear redirect url and messages
|
||||||
const routeUrl$ = this.store.pipe(
|
const routeUrl$ = this.store.pipe(
|
||||||
select(routerStateSelector),
|
select(routerStateSelector),
|
||||||
filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)),
|
filter((routerState: RouterReducerState) => isNotUndefined(routerState)
|
||||||
|
&& isNotUndefined(routerState.state) && isNotEmpty(routerState.state.url)),
|
||||||
filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)),
|
filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)),
|
||||||
map((routerState: RouterReducerState) => routerState.state.url)
|
map((routerState: RouterReducerState) => routerState.state.url)
|
||||||
);
|
);
|
||||||
|
36
src/app/core/cache/models/normalized-external-source-entry.model.ts
vendored
Normal file
36
src/app/core/cache/models/normalized-external-source-entry.model.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||||
|
import { NormalizedObject } from './normalized-object.model';
|
||||||
|
import { ExternalSourceEntry } from '../../shared/external-source-entry.model';
|
||||||
|
import { mapsTo } from '../builders/build-decorators';
|
||||||
|
import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized model class for an external source entry
|
||||||
|
*/
|
||||||
|
@mapsTo(ExternalSourceEntry)
|
||||||
|
@inheritSerialization(NormalizedObject)
|
||||||
|
export class NormalizedExternalSourceEntry extends NormalizedObject<ExternalSourceEntry> {
|
||||||
|
/**
|
||||||
|
* Unique identifier
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value to display
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
display: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value to store the entry with
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata of the entry
|
||||||
|
*/
|
||||||
|
@autoserializeAs(MetadataMapSerializer)
|
||||||
|
metadata: MetadataMap;
|
||||||
|
}
|
29
src/app/core/cache/models/normalized-external-source.model.ts
vendored
Normal file
29
src/app/core/cache/models/normalized-external-source.model.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||||
|
import { NormalizedObject } from './normalized-object.model';
|
||||||
|
import { ExternalSource } from '../../shared/external-source.model';
|
||||||
|
import { mapsTo } from '../builders/build-decorators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized model class for an external source
|
||||||
|
*/
|
||||||
|
@mapsTo(ExternalSource)
|
||||||
|
@inheritSerialization(NormalizedObject)
|
||||||
|
export class NormalizedExternalSource extends NormalizedObject<ExternalSource> {
|
||||||
|
/**
|
||||||
|
* Unique identifier
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this external source
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the source hierarchical?
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
hierarchical: boolean;
|
||||||
|
}
|
@@ -136,6 +136,10 @@ import { SearchConfigurationService } from './shared/search/search-configuration
|
|||||||
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { RelationshipTypeService } from './data/relationship-type.service';
|
import { RelationshipTypeService } from './data/relationship-type.service';
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
|
import { NormalizedExternalSource } from './cache/models/normalized-external-source.model';
|
||||||
|
import { NormalizedExternalSourceEntry } from './cache/models/normalized-external-source-entry.model';
|
||||||
|
import { ExternalSourceService } from './data/external-source.service';
|
||||||
|
import { LookupRelationService } from './data/lookup-relation.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -247,6 +251,8 @@ const PROVIDERS = [
|
|||||||
SearchConfigurationService,
|
SearchConfigurationService,
|
||||||
SelectableListService,
|
SelectableListService,
|
||||||
RelationshipTypeService,
|
RelationshipTypeService,
|
||||||
|
ExternalSourceService,
|
||||||
|
LookupRelationService,
|
||||||
// register AuthInterceptor as HttpInterceptor
|
// register AuthInterceptor as HttpInterceptor
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
@@ -292,7 +298,9 @@ export const normalizedModels =
|
|||||||
NormalizedPoolTask,
|
NormalizedPoolTask,
|
||||||
NormalizedRelationship,
|
NormalizedRelationship,
|
||||||
NormalizedRelationshipType,
|
NormalizedRelationshipType,
|
||||||
NormalizedItemType
|
NormalizedItemType,
|
||||||
|
NormalizedExternalSource,
|
||||||
|
NormalizedExternalSourceEntry
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { ActionReducerMap, } from '@ngrx/store';
|
||||||
ActionReducerMap,
|
|
||||||
createFeatureSelector,
|
|
||||||
} from '@ngrx/store';
|
|
||||||
|
|
||||||
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
||||||
import { indexReducer, MetaIndexState } from './index/index.reducer';
|
import { indexReducer, MetaIndexState } from './index/index.reducer';
|
||||||
@@ -9,17 +6,21 @@ import { requestReducer, RequestState } from './data/request.reducer';
|
|||||||
import { authReducer, AuthState } from './auth/auth.reducer';
|
import { authReducer, AuthState } from './auth/auth.reducer';
|
||||||
import { jsonPatchOperationsReducer, JsonPatchOperationsState } from './json-patch/json-patch-operations.reducer';
|
import { jsonPatchOperationsReducer, JsonPatchOperationsState } from './json-patch/json-patch-operations.reducer';
|
||||||
import { serverSyncBufferReducer, ServerSyncBufferState } from './cache/server-sync-buffer.reducer';
|
import { serverSyncBufferReducer, ServerSyncBufferState } from './cache/server-sync-buffer.reducer';
|
||||||
import {
|
import { objectUpdatesReducer, ObjectUpdatesState } from './data/object-updates/object-updates.reducer';
|
||||||
objectUpdatesReducer,
|
|
||||||
ObjectUpdatesState
|
|
||||||
} from './data/object-updates/object-updates.reducer';
|
|
||||||
import { routeReducer, RouteState } from './services/route.reducer';
|
import { routeReducer, RouteState } from './services/route.reducer';
|
||||||
|
import {
|
||||||
|
bitstreamFormatReducer,
|
||||||
|
BitstreamFormatRegistryState
|
||||||
|
} from '../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
||||||
|
import { historyReducer, HistoryState } from './history/history.reducer';
|
||||||
|
|
||||||
export interface CoreState {
|
export interface CoreState {
|
||||||
|
'bitstreamFormats': BitstreamFormatRegistryState;
|
||||||
'cache/object': ObjectCacheState,
|
'cache/object': ObjectCacheState,
|
||||||
'cache/syncbuffer': ServerSyncBufferState,
|
'cache/syncbuffer': ServerSyncBufferState,
|
||||||
'cache/object-updates': ObjectUpdatesState
|
'cache/object-updates': ObjectUpdatesState
|
||||||
'data/request': RequestState,
|
'data/request': RequestState,
|
||||||
|
'history': HistoryState;
|
||||||
'index': MetaIndexState,
|
'index': MetaIndexState,
|
||||||
'auth': AuthState,
|
'auth': AuthState,
|
||||||
'json/patch': JsonPatchOperationsState,
|
'json/patch': JsonPatchOperationsState,
|
||||||
@@ -27,10 +28,12 @@ export interface CoreState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||||
|
'bitstreamFormats': bitstreamFormatReducer,
|
||||||
'cache/object': objectCacheReducer,
|
'cache/object': objectCacheReducer,
|
||||||
'cache/syncbuffer': serverSyncBufferReducer,
|
'cache/syncbuffer': serverSyncBufferReducer,
|
||||||
'cache/object-updates': objectUpdatesReducer,
|
'cache/object-updates': objectUpdatesReducer,
|
||||||
'data/request': requestReducer,
|
'data/request': requestReducer,
|
||||||
|
'history': historyReducer,
|
||||||
'index': indexReducer,
|
'index': indexReducer,
|
||||||
'auth': authReducer,
|
'auth': authReducer,
|
||||||
'json/patch': jsonPatchOperationsReducer,
|
'json/patch': jsonPatchOperationsReducer,
|
||||||
|
@@ -3,7 +3,6 @@ import { RequestEntry } from './request.reducer';
|
|||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, Store } from '@ngrx/store';
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
BitstreamFormatsRegistrySelectAction
|
BitstreamFormatsRegistrySelectAction
|
||||||
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
|
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
|
||||||
describe('BitstreamFormatDataService', () => {
|
describe('BitstreamFormatDataService', () => {
|
||||||
let service: BitstreamFormatDataService;
|
let service: BitstreamFormatDataService;
|
||||||
|
@@ -5,7 +5,6 @@ import { RequestService } from './request.service';
|
|||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { createSelector, select, Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
@@ -17,7 +16,6 @@ import { find, map, tap } from 'rxjs/operators';
|
|||||||
import { configureRequest, getResponseFromEntry } from '../shared/operators';
|
import { configureRequest, getResponseFromEntry } from '../shared/operators';
|
||||||
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
|
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { AppState } from '../../app.reducer';
|
|
||||||
import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
||||||
import {
|
import {
|
||||||
BitstreamFormatsRegistryDeselectAction,
|
BitstreamFormatsRegistryDeselectAction,
|
||||||
@@ -26,8 +24,9 @@ import {
|
|||||||
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
|
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { RequestEntry } from './request.reducer';
|
import { RequestEntry } from './request.reducer';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
|
||||||
const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
|
const bitstreamFormatsStateSelector = (state: CoreState) => state.bitstreamFormats;
|
||||||
const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
|
const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
|
||||||
(bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
|
(bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
|
||||||
|
|
||||||
@@ -55,6 +54,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
|||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing bitstream formats
|
* Get the endpoint for browsing bitstream formats
|
||||||
* @param {FindListOptions} options
|
* @param {FindListOptions} options
|
||||||
|
* @param {string} linkPath
|
||||||
* @returns {Observable<string>}
|
* @returns {Observable<string>}
|
||||||
*/
|
*/
|
||||||
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||||
@@ -99,7 +99,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new BitstreamFormat
|
* Create a new BitstreamFormat
|
||||||
* @param BitstreamFormat
|
* @param {BitstreamFormat} bitstreamFormat
|
||||||
*/
|
*/
|
||||||
public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
|
public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
@@ -1,12 +1,22 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, find, first, map, mergeMap, skipWhile, switchMap, take, tap } from 'rxjs/operators';
|
import {
|
||||||
|
distinctUntilChanged,
|
||||||
|
filter,
|
||||||
|
find,
|
||||||
|
first,
|
||||||
|
map,
|
||||||
|
mergeMap,
|
||||||
|
skipWhile,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
tap
|
||||||
|
} from 'rxjs/operators';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
@@ -14,9 +24,9 @@ import { RemoteData } from './remote-data';
|
|||||||
import {
|
import {
|
||||||
CreateRequest,
|
CreateRequest,
|
||||||
DeleteByIDRequest,
|
DeleteByIDRequest,
|
||||||
|
FindByIDRequest,
|
||||||
FindListOptions,
|
FindListOptions,
|
||||||
FindListRequest,
|
FindListRequest,
|
||||||
FindByIDRequest,
|
|
||||||
GetRequest
|
GetRequest
|
||||||
} from './request.models';
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
@@ -37,6 +47,7 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
|
|||||||
import { ChangeAnalyzer } from './change-analyzer';
|
import { ChangeAnalyzer } from './change-analyzer';
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
import { getMapsToType } from '../cache/builders/build-decorators';
|
import { getMapsToType } from '../cache/builders/build-decorators';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
|
||||||
export abstract class DataService<T extends CacheableObject> {
|
export abstract class DataService<T extends CacheableObject> {
|
||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
@@ -238,7 +249,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
*/
|
*/
|
||||||
update(object: T): Observable<RemoteData<T>> {
|
update(object: T): Observable<RemoteData<T>> {
|
||||||
const oldVersion$ = this.objectCache.getObjectBySelfLink(object.self);
|
const oldVersion$ = this.objectCache.getObjectBySelfLink(object.self);
|
||||||
return oldVersion$.pipe(take(1), mergeMap((oldVersion: T) => {
|
return oldVersion$.pipe(take(1), mergeMap((oldVersion: NormalizedObject<T>) => {
|
||||||
const operations = this.comparator.diff(oldVersion, object);
|
const operations = this.comparator.diff(oldVersion, object);
|
||||||
if (isNotEmpty(operations)) {
|
if (isNotEmpty(operations)) {
|
||||||
this.objectCache.addPatch(object.self, operations);
|
this.objectCache.addPatch(object.self, operations);
|
||||||
|
76
src/app/core/data/external-source.service.spec.ts
Normal file
76
src/app/core/data/external-source.service.spec.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { ExternalSourceService } from './external-source.service';
|
||||||
|
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { GetRequest } from './request.models';
|
||||||
|
|
||||||
|
describe('ExternalSourceService', () => {
|
||||||
|
let service: ExternalSourceService;
|
||||||
|
|
||||||
|
let requestService;
|
||||||
|
let rdbService;
|
||||||
|
let halService;
|
||||||
|
|
||||||
|
const entries = [
|
||||||
|
Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0001'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0002',
|
||||||
|
display: 'Sampson Megan',
|
||||||
|
value: 'Sampson, Megan',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0002'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: 'request-uuid',
|
||||||
|
configure: {}
|
||||||
|
});
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildList: createSuccessfulRemoteDataObject$(createPaginatedList(entries))
|
||||||
|
});
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: observableOf('external-sources-REST-endpoint')
|
||||||
|
});
|
||||||
|
service = new ExternalSourceService(requestService, rdbService, undefined, undefined, undefined, halService, undefined, undefined, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getExternalSourceEntries', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = service.getExternalSourceEntries('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure a GetRequest', () => {
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the entries', () => {
|
||||||
|
result.subscribe((resultRD) => {
|
||||||
|
expect(resultRD.payload.page).toBe(entries);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
85
src/app/core/data/external-source.service.ts
Normal file
85
src/app/core/data/external-source.service.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DataService } from './data.service';
|
||||||
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { FindListOptions, GetRequest } from './request.models';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
|
import { configureRequest } from '../shared/operators';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service handling all external source requests
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ExternalSourceService extends DataService<ExternalSource> {
|
||||||
|
protected linkPath = 'externalsources';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected dataBuildService: NormalizedObjectBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<ExternalSource>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint to browse external sources
|
||||||
|
* @param options
|
||||||
|
* @param linkPath
|
||||||
|
*/
|
||||||
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(linkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint for an external source's entries
|
||||||
|
* @param externalSourceId The id of the external source to fetch entries for
|
||||||
|
*/
|
||||||
|
getEntriesEndpoint(externalSourceId: string): Observable<string> {
|
||||||
|
return this.getBrowseEndpoint().pipe(
|
||||||
|
map((href) => this.getIDHref(href, externalSourceId)),
|
||||||
|
switchMap((href) => this.halService.getEndpoint('entries', href))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entries for an external source
|
||||||
|
* @param externalSourceId The id of the external source to fetch entries for
|
||||||
|
* @param searchOptions The search options to limit results to
|
||||||
|
*/
|
||||||
|
getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<ExternalSourceEntry>>> {
|
||||||
|
const requestUuid = this.requestService.generateRequestId();
|
||||||
|
|
||||||
|
const href$ = this.getEntriesEndpoint(externalSourceId).pipe(
|
||||||
|
isNotEmptyOperator(),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
|
||||||
|
);
|
||||||
|
|
||||||
|
href$.pipe(
|
||||||
|
map((endpoint: string) => new GetRequest(requestUuid, endpoint)),
|
||||||
|
configureRequest(this.requestService)
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.rdbService.buildList(href$);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,22 +6,14 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import {
|
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
DeleteRequest,
|
|
||||||
FindListOptions,
|
|
||||||
GetRequest,
|
|
||||||
MappedCollectionsRequest,
|
|
||||||
PostRequest,
|
|
||||||
RestRequest
|
|
||||||
} from './request.models';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { RequestEntry } from './request.reducer';
|
import { RequestEntry } from './request.reducer';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
|
|
||||||
describe('ItemDataService', () => {
|
describe('ItemDataService', () => {
|
||||||
@@ -184,7 +176,7 @@ describe('ItemDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should configure a DELETE request', () => {
|
it('should configure a DELETE request', () => {
|
||||||
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest), undefined));
|
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,7 +190,7 @@ describe('ItemDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should configure a POST request', () => {
|
it('should configure a POST request', () => {
|
||||||
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest), undefined));
|
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
116
src/app/core/data/lookup-relation.service.spec.ts
Normal file
116
src/app/core/data/lookup-relation.service.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { LookupRelationService } from './lookup-relation.service';
|
||||||
|
import { ExternalSourceService } from './external-source.service';
|
||||||
|
import { SearchService } from '../shared/search/search.service';
|
||||||
|
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { skip, take } from 'rxjs/operators';
|
||||||
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
|
||||||
|
describe('LookupRelationService', () => {
|
||||||
|
let service: LookupRelationService;
|
||||||
|
let externalSourceService: ExternalSourceService;
|
||||||
|
let searchService: SearchService;
|
||||||
|
|
||||||
|
const totalExternal = 8;
|
||||||
|
const optionsWithQuery = new PaginatedSearchOptions({ query: 'test-query' });
|
||||||
|
const relationship = Object.assign(new RelationshipOptions(), {
|
||||||
|
filter: 'test-filter',
|
||||||
|
configuration: 'test-configuration'
|
||||||
|
});
|
||||||
|
const localResults = [
|
||||||
|
Object.assign(new SearchResult(), {
|
||||||
|
indexableObject: Object.assign(new Item(), {
|
||||||
|
uuid: 'test-item-uuid',
|
||||||
|
handle: 'test-item-handle'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
];
|
||||||
|
const externalSource = Object.assign(new ExternalSource(), {
|
||||||
|
id: 'orcidV2',
|
||||||
|
name: 'orcidV2',
|
||||||
|
hierarchical: false
|
||||||
|
});
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
||||||
|
getExternalSourceEntries: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: totalExternal, totalPages: totalExternal, currentPage: 1 }), [{}]))
|
||||||
|
});
|
||||||
|
searchService = jasmine.createSpyObj('searchService', {
|
||||||
|
search: createSuccessfulRemoteDataObject$(createPaginatedList(localResults))
|
||||||
|
});
|
||||||
|
service = new LookupRelationService(externalSourceService, searchService);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getLocalResults', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = service.getLocalResults(relationship, optionsWithQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the local results', () => {
|
||||||
|
result.subscribe((resultsRD) => {
|
||||||
|
expect(resultsRD.payload.page).toBe(localResults);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the searchConfig to contain a fixedFilter and configuration', () => {
|
||||||
|
expect(service.searchConfig).toEqual(Object.assign(new PaginatedSearchOptions({}), optionsWithQuery,
|
||||||
|
{ fixedFilter: relationship.filter, configuration: relationship.searchConfiguration }
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTotalLocalResults', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = service.getTotalLocalResults(relationship, optionsWithQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with 0', () => {
|
||||||
|
result.pipe(take(1)).subscribe((amount) => {
|
||||||
|
expect(amount).toEqual(0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct total amount', () => {
|
||||||
|
result.pipe(skip(1)).subscribe((amount) => {
|
||||||
|
expect(amount).toEqual(localResults.length)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set searchConfig', () => {
|
||||||
|
expect(service.searchConfig).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTotalExternalResults', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = service.getTotalExternalResults(externalSource, optionsWithQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with 0', () => {
|
||||||
|
result.pipe(take(1)).subscribe((amount) => {
|
||||||
|
expect(amount).toEqual(0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct total amount', () => {
|
||||||
|
result.pipe(skip(1)).subscribe((amount) => {
|
||||||
|
expect(amount).toEqual(totalExternal)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
94
src/app/core/data/lookup-relation.service.ts
Normal file
94
src/app/core/data/lookup-relation.service.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { ExternalSourceService } from './external-source.service';
|
||||||
|
import { SearchService } from '../shared/search/search.service';
|
||||||
|
import { concat, map, multicast, startWith, take, takeWhile } from 'rxjs/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
import { ReplaySubject } from 'rxjs/internal/ReplaySubject';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
import { RelationshipOptions } from '../../shared/form/builder/models/relationship-options.model';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../shared/operators';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service for retrieving local and external entries information during a relation lookup
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class LookupRelationService {
|
||||||
|
/**
|
||||||
|
* The search config last used for retrieving local results
|
||||||
|
*/
|
||||||
|
public searchConfig: PaginatedSearchOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination options for retrieving exactly one result
|
||||||
|
*/
|
||||||
|
private singleResultOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'single-result-options',
|
||||||
|
pageSize: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(protected externalSourceService: ExternalSourceService,
|
||||||
|
protected searchService: SearchService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the available local entries for a relationship
|
||||||
|
* @param relationship Relationship options
|
||||||
|
* @param searchOptions Search options to filter results
|
||||||
|
* @param setSearchConfig Optionally choose if we should store the used search config in a local variable (defaults to true)
|
||||||
|
*/
|
||||||
|
getLocalResults(relationship: RelationshipOptions, searchOptions: PaginatedSearchOptions, setSearchConfig = true): Observable<RemoteData<PaginatedList<SearchResult<Item>>>> {
|
||||||
|
const newConfig = Object.assign(new PaginatedSearchOptions({}), searchOptions,
|
||||||
|
{ fixedFilter: relationship.filter, configuration: relationship.searchConfiguration }
|
||||||
|
);
|
||||||
|
if (setSearchConfig) {
|
||||||
|
this.searchConfig = newConfig;
|
||||||
|
}
|
||||||
|
return this.searchService.search(newConfig).pipe(
|
||||||
|
/* Make sure to only listen to the first x results, until loading is finished */
|
||||||
|
/* TODO: in Rxjs 6.4.0 and up, we can replace this with takeWhile(predicate, true) - see https://stackoverflow.com/a/44644237 */
|
||||||
|
multicast(
|
||||||
|
() => new ReplaySubject(1),
|
||||||
|
(subject) => subject.pipe(
|
||||||
|
takeWhile((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => rd.isLoading),
|
||||||
|
concat(subject.pipe(take(1)))
|
||||||
|
)
|
||||||
|
) as any
|
||||||
|
) as Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the total local entries available for the given relationship
|
||||||
|
* @param relationship Relationship options
|
||||||
|
* @param searchOptions Search options to filter results
|
||||||
|
*/
|
||||||
|
getTotalLocalResults(relationship: RelationshipOptions, searchOptions: PaginatedSearchOptions): Observable<number> {
|
||||||
|
return this.getLocalResults(relationship, Object.assign(new PaginatedSearchOptions({}), searchOptions, { pagination: this.singleResultOptions }), false).pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((results: PaginatedList<SearchResult<Item>>) => results.totalElements),
|
||||||
|
startWith(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the total external entries available for a given external source
|
||||||
|
* @param externalSource External Source
|
||||||
|
* @param searchOptions Search options to filter results
|
||||||
|
*/
|
||||||
|
getTotalExternalResults(externalSource: ExternalSource, searchOptions: PaginatedSearchOptions): Observable<number> {
|
||||||
|
return this.externalSourceService.getExternalSourceEntries(externalSource.id, Object.assign(new PaginatedSearchOptions({}), searchOptions, { pagination: this.singleResultOptions })).pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((results: PaginatedList<ExternalSourceEntry>) => results.totalElements),
|
||||||
|
startWith(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { filter, find, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, find, map, switchMap } from 'rxjs/operators';
|
||||||
import { configureRequest, getSucceededRemoteData } from '../shared/operators';
|
import { configureRequest, getSucceededRemoteData } from '../shared/operators';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
@@ -42,7 +42,7 @@ export class RelationshipTypeService {
|
|||||||
map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
|
map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
|
||||||
configureRequest(this.requestService),
|
configureRequest(this.requestService),
|
||||||
switchMap(() => this.rdbService.buildList(link$))
|
switchMap(() => this.rdbService.buildList(link$))
|
||||||
);
|
) as Observable<RemoteData<PaginatedList<RelationshipType>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,8 +3,13 @@ import { RequestService } from './request.service';
|
|||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { distinctUntilChanged, filter, map, mergeMap, skipWhile, startWith, switchMap, take, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
import {
|
||||||
|
configureRequest,
|
||||||
|
getRemoteDataPayload,
|
||||||
|
getResponseFromEntry,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../shared/operators';
|
||||||
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
@@ -15,7 +20,11 @@ import { RemoteData } from './remote-data';
|
|||||||
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
import {
|
||||||
|
compareArraysUsingIds,
|
||||||
|
paginatedRelationsToItems,
|
||||||
|
relationsToItems
|
||||||
|
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
@@ -28,7 +37,10 @@ import { SearchParam } from '../cache/models/search-param.model';
|
|||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
import {
|
||||||
|
RemoveNameVariantAction,
|
||||||
|
SetNameVariantAction
|
||||||
|
} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
||||||
|
|
||||||
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
||||||
|
|
||||||
@@ -117,7 +129,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
getResponseFromEntry(),
|
getResponseFromEntry(),
|
||||||
tap(() => this.removeRelationshipItemsFromCache(item1)),
|
tap(() => this.removeRelationshipItemsFromCache(item1)),
|
||||||
tap(() => this.removeRelationshipItemsFromCache(item2))
|
tap(() => this.removeRelationshipItemsFromCache(item2))
|
||||||
);
|
) as Observable<RestResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -21,6 +21,7 @@ import {
|
|||||||
} from './request.models';
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RequestEntry } from './request.reducer';
|
||||||
|
|
||||||
describe('RequestService', () => {
|
describe('RequestService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -107,7 +108,7 @@ describe('RequestService', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByHref').and.returnValue(observableOf({
|
spyOn(service, 'getByHref').and.returnValue(observableOf({
|
||||||
completed: false
|
completed: false
|
||||||
}))
|
} as RequestEntry))
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', () => {
|
it('should return true', () => {
|
||||||
@@ -122,7 +123,7 @@ describe('RequestService', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByHref').and.returnValues(observableOf({
|
spyOn(service, 'getByHref').and.returnValues(observableOf({
|
||||||
completed: true
|
completed: true
|
||||||
}));
|
} as RequestEntry));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false', () => {
|
it('should return false', () => {
|
||||||
@@ -432,7 +433,7 @@ describe('RequestService', () => {
|
|||||||
let valid;
|
let valid;
|
||||||
const requestEntry = { completed: false };
|
const requestEntry = { completed: false };
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry as RequestEntry));
|
||||||
valid = serviceAsAny.isValid(requestEntry);
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
});
|
});
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
@@ -444,7 +445,7 @@ describe('RequestService', () => {
|
|||||||
let valid;
|
let valid;
|
||||||
const requestEntry = { completed: true, response: { isSuccessful: false } };
|
const requestEntry = { completed: true, response: { isSuccessful: false } };
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry as RequestEntry));
|
||||||
valid = serviceAsAny.isValid(requestEntry);
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
});
|
});
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
@@ -470,7 +471,7 @@ describe('RequestService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry as RequestEntry));
|
||||||
valid = serviceAsAny.isValid(requestEntry);
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -497,7 +498,7 @@ describe('RequestService', () => {
|
|||||||
};
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry as RequestEntry));
|
||||||
valid = serviceAsAny.isValid(requestEntry);
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,8 +5,6 @@ import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
|||||||
import { Observable, race as observableRace } from 'rxjs';
|
import { Observable, race as observableRace } from 'rxjs';
|
||||||
import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
|
import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
|
||||||
import { cloneDeep, remove } from 'lodash';
|
import { cloneDeep, remove } from 'lodash';
|
||||||
|
|
||||||
import { AppState } from '../../app.reducer';
|
|
||||||
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -19,7 +17,7 @@ import {
|
|||||||
} from '../index/index.selectors';
|
} from '../index/index.selectors';
|
||||||
import { UUIDService } from '../shared/uuid.service';
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions';
|
import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions';
|
||||||
import { GetRequest, RestRequest, SubmissionRequest } from './request.models';
|
import { GetRequest, RestRequest } from './request.models';
|
||||||
import { RequestEntry, RequestState } from './request.reducer';
|
import { RequestEntry, RequestState } from './request.reducer';
|
||||||
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
|
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
@@ -52,7 +50,7 @@ const entryFromUUIDSelector = (uuid: string): MemoizedSelector<CoreState, Reques
|
|||||||
* @param href Substring that the request's href should contain
|
* @param href Substring that the request's href should contain
|
||||||
*/
|
*/
|
||||||
const uuidsFromHrefSubstringSelector =
|
const uuidsFromHrefSubstringSelector =
|
||||||
(selector: MemoizedSelector<AppState, IndexState>, href: string): MemoizedSelector<AppState, string[]> => createSelector(
|
(selector: MemoizedSelector<CoreState, IndexState>, href: string): MemoizedSelector<CoreState, string[]> => createSelector(
|
||||||
selector,
|
selector,
|
||||||
(state: IndexState) => getUuidsFromHrefSubstring(state, href)
|
(state: IndexState) => getUuidsFromHrefSubstring(state, href)
|
||||||
);
|
);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
import { type } from '../ngrx/type';
|
import { type } from '../../shared/ngrx/type';
|
||||||
|
|
||||||
export const HistoryActionTypes = {
|
export const HistoryActionTypes = {
|
||||||
ADD_TO_HISTORY: type('dspace/history/ADD_TO_HISTORY'),
|
ADD_TO_HISTORY: type('dspace/history/ADD_TO_HISTORY'),
|
3
src/app/core/history/selectors.ts
Normal file
3
src/app/core/history/selectors.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
|
||||||
|
export const historySelector = (state: CoreState) => state.history;
|
@@ -1,5 +1,4 @@
|
|||||||
import { createSelector, MemoizedSelector } from '@ngrx/store';
|
import { createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import { AppState } from '../../app.reducer';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { coreSelector } from '../core.selectors';
|
import { coreSelector } from '../core.selectors';
|
||||||
@@ -11,7 +10,7 @@ import { IndexName, IndexState, MetaIndexState } from './index.reducer';
|
|||||||
* @returns
|
* @returns
|
||||||
* a MemoizedSelector to select the MetaIndexState
|
* a MemoizedSelector to select the MetaIndexState
|
||||||
*/
|
*/
|
||||||
export const metaIndexSelector: MemoizedSelector<AppState, MetaIndexState> = createSelector(
|
export const metaIndexSelector: MemoizedSelector<CoreState, MetaIndexState> = createSelector(
|
||||||
coreSelector,
|
coreSelector,
|
||||||
(state: CoreState) => state.index
|
(state: CoreState) => state.index
|
||||||
);
|
);
|
||||||
@@ -23,7 +22,7 @@ export const metaIndexSelector: MemoizedSelector<AppState, MetaIndexState> = cre
|
|||||||
* @returns
|
* @returns
|
||||||
* a MemoizedSelector to select the object index
|
* a MemoizedSelector to select the object index
|
||||||
*/
|
*/
|
||||||
export const objectIndexSelector: MemoizedSelector<AppState, IndexState> = createSelector(
|
export const objectIndexSelector: MemoizedSelector<CoreState, IndexState> = createSelector(
|
||||||
metaIndexSelector,
|
metaIndexSelector,
|
||||||
(state: MetaIndexState) => state[IndexName.OBJECT]
|
(state: MetaIndexState) => state[IndexName.OBJECT]
|
||||||
);
|
);
|
||||||
@@ -34,7 +33,7 @@ export const objectIndexSelector: MemoizedSelector<AppState, IndexState> = creat
|
|||||||
* @returns
|
* @returns
|
||||||
* a MemoizedSelector to select the request index
|
* a MemoizedSelector to select the request index
|
||||||
*/
|
*/
|
||||||
export const requestIndexSelector: MemoizedSelector<AppState, IndexState> = createSelector(
|
export const requestIndexSelector: MemoizedSelector<CoreState, IndexState> = createSelector(
|
||||||
metaIndexSelector,
|
metaIndexSelector,
|
||||||
(state: MetaIndexState) => state[IndexName.REQUEST]
|
(state: MetaIndexState) => state[IndexName.REQUEST]
|
||||||
);
|
);
|
||||||
@@ -45,7 +44,7 @@ export const requestIndexSelector: MemoizedSelector<AppState, IndexState> = crea
|
|||||||
* @returns
|
* @returns
|
||||||
* a MemoizedSelector to select the request UUID mapping
|
* a MemoizedSelector to select the request UUID mapping
|
||||||
*/
|
*/
|
||||||
export const requestUUIDIndexSelector: MemoizedSelector<AppState, IndexState> = createSelector(
|
export const requestUUIDIndexSelector: MemoizedSelector<CoreState, IndexState> = createSelector(
|
||||||
metaIndexSelector,
|
metaIndexSelector,
|
||||||
(state: MetaIndexState) => state[IndexName.UUID_MAPPING]
|
(state: MetaIndexState) => state[IndexName.UUID_MAPPING]
|
||||||
);
|
);
|
||||||
@@ -53,14 +52,13 @@ export const requestUUIDIndexSelector: MemoizedSelector<AppState, IndexState> =
|
|||||||
/**
|
/**
|
||||||
* Return the self link of an object in the object-cache based on its UUID
|
* Return the self link of an object in the object-cache based on its UUID
|
||||||
*
|
*
|
||||||
* @param id
|
* @param uuid
|
||||||
* the UUID for which you want to find the matching self link
|
* the UUID for which you want to find the matching self link
|
||||||
* @param identifierType the type of index, used to select index from state
|
|
||||||
* @returns
|
* @returns
|
||||||
* a MemoizedSelector to select the self link
|
* a MemoizedSelector to select the self link
|
||||||
*/
|
*/
|
||||||
export const selfLinkFromUuidSelector =
|
export const selfLinkFromUuidSelector =
|
||||||
(uuid: string): MemoizedSelector<AppState, string> => createSelector(
|
(uuid: string): MemoizedSelector<CoreState, string> => createSelector(
|
||||||
objectIndexSelector,
|
objectIndexSelector,
|
||||||
(state: IndexState) => hasValue(state) ? state[uuid] : undefined
|
(state: IndexState) => hasValue(state) ? state[uuid] : undefined
|
||||||
);
|
);
|
||||||
@@ -74,7 +72,7 @@ export const selfLinkFromUuidSelector =
|
|||||||
* a MemoizedSelector to select the UUID
|
* a MemoizedSelector to select the UUID
|
||||||
*/
|
*/
|
||||||
export const uuidFromHrefSelector =
|
export const uuidFromHrefSelector =
|
||||||
(href: string): MemoizedSelector<AppState, string> => createSelector(
|
(href: string): MemoizedSelector<CoreState, string> => createSelector(
|
||||||
requestIndexSelector,
|
requestIndexSelector,
|
||||||
(state: IndexState) => hasValue(state) ? state[href] : undefined
|
(state: IndexState) => hasValue(state) ? state[href] : undefined
|
||||||
);
|
);
|
||||||
@@ -89,7 +87,7 @@ export const uuidFromHrefSelector =
|
|||||||
* a MemoizedSelector to select the UUID of the cached request
|
* a MemoizedSelector to select the UUID of the cached request
|
||||||
*/
|
*/
|
||||||
export const originalRequestUUIDFromRequestUUIDSelector =
|
export const originalRequestUUIDFromRequestUUIDSelector =
|
||||||
(uuid: string): MemoizedSelector<AppState, string> => createSelector(
|
(uuid: string): MemoizedSelector<CoreState, string> => createSelector(
|
||||||
requestUUIDIndexSelector,
|
requestUUIDIndexSelector,
|
||||||
(state: IndexState) => hasValue(state) ? state[uuid] : undefined
|
(state: IndexState) => hasValue(state) ? state[uuid] : undefined
|
||||||
);
|
);
|
||||||
|
@@ -17,7 +17,6 @@ import {
|
|||||||
} from './json-patch-operations.actions';
|
} from './json-patch-operations.actions';
|
||||||
import { JsonPatchOperationModel } from './json-patch.model';
|
import { JsonPatchOperationModel } from './json-patch.model';
|
||||||
import { getResponseFromEntry } from '../shared/operators';
|
import { getResponseFromEntry } from '../shared/operators';
|
||||||
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract class that provides methods to make JSON Patch requests.
|
* An abstract class that provides methods to make JSON Patch requests.
|
||||||
@@ -88,8 +87,8 @@ export abstract class JsonPatchOperationsService<ResponseDefinitionDomain, Patch
|
|||||||
flatMap(() => {
|
flatMap(() => {
|
||||||
const [successResponse$, errorResponse$] = partition((response: RestResponse) => response.isSuccessful)(this.requestService.getByUUID(requestId).pipe(
|
const [successResponse$, errorResponse$] = partition((response: RestResponse) => response.isSuccessful)(this.requestService.getByUUID(requestId).pipe(
|
||||||
getResponseFromEntry(),
|
getResponseFromEntry(),
|
||||||
find((entry: ObjectCacheEntry) => startTransactionTime < entry.timeAdded),
|
find((entry: RestResponse) => startTransactionTime < entry.timeAdded),
|
||||||
map((entry: ObjectCacheEntry) => entry),
|
map((entry: RestResponse) => entry),
|
||||||
));
|
));
|
||||||
return observableMerge(
|
return observableMerge(
|
||||||
errorResponse$.pipe(
|
errorResponse$.pipe(
|
||||||
|
@@ -8,7 +8,7 @@ import { getTestScheduler, hot } from 'jasmine-marbles';
|
|||||||
import { RouteService } from './route.service';
|
import { RouteService } from './route.service';
|
||||||
import { MockRouter } from '../../shared/mocks/mock-router';
|
import { MockRouter } from '../../shared/mocks/mock-router';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
|
import { AddUrlToHistoryAction } from '../history/history.actions';
|
||||||
|
|
||||||
describe('RouteService', () => {
|
describe('RouteService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
|
@@ -1,28 +1,17 @@
|
|||||||
import { distinctUntilChanged, filter, map, take, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, take } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import { ActivatedRoute, NavigationEnd, Params, Router, RouterStateSnapshot, } from '@angular/router';
|
||||||
ActivatedRoute,
|
|
||||||
NavigationEnd,
|
|
||||||
Params,
|
|
||||||
Router,
|
|
||||||
RouterStateSnapshot,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import {
|
import { AddParameterAction, SetParameterAction, SetParametersAction, SetQueryParametersAction } from './route.actions';
|
||||||
AddParameterAction,
|
import { CoreState } from '../core.reducers';
|
||||||
SetParameterAction,
|
import { coreSelector } from '../core.selectors';
|
||||||
SetParametersAction,
|
|
||||||
SetQueryParametersAction
|
|
||||||
} from './route.actions';
|
|
||||||
import { CoreState } from '../../core/core.reducers';
|
|
||||||
import { coreSelector } from '../../core/core.selectors';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { historySelector } from '../../shared/history/selectors';
|
import { historySelector } from '../history/selectors';
|
||||||
import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
|
import { AddUrlToHistoryAction } from '../history/history.actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector to select all route parameters from the store
|
* Selector to select all route parameters from the store
|
||||||
@@ -187,10 +176,20 @@ export class RouteService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a parameter to the current route
|
||||||
|
* @param key The parameter name
|
||||||
|
* @param value The parameter value
|
||||||
|
*/
|
||||||
public addParameter(key, value) {
|
public addParameter(key, value) {
|
||||||
this.store.dispatch(new AddParameterAction(key, value));
|
this.store.dispatch(new AddParameterAction(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a parameter in the current route (overriding the previous value)
|
||||||
|
* @param key The parameter name
|
||||||
|
* @param value The parameter value
|
||||||
|
*/
|
||||||
public setParameter(key, value) {
|
public setParameter(key, value) {
|
||||||
this.store.dispatch(new SetParameterAction(key, value));
|
this.store.dispatch(new SetParameterAction(key, value));
|
||||||
}
|
}
|
||||||
|
43
src/app/core/shared/external-source-entry.model.ts
Normal file
43
src/app/core/shared/external-source-entry.model.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { MetadataMap } from './metadata.models';
|
||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||||
|
import { GenericConstructor } from './generic-constructor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for a single entry from an external source
|
||||||
|
*/
|
||||||
|
export class ExternalSourceEntry extends ListableObject {
|
||||||
|
static type = new ResourceType('externalSourceEntry');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifier
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value to display
|
||||||
|
*/
|
||||||
|
display: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value to store the entry with
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata of the entry
|
||||||
|
*/
|
||||||
|
metadata: MetadataMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The link to the rest endpoint where this External Source Entry can be found
|
||||||
|
*/
|
||||||
|
self: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that returns as which type of object this object should be rendered
|
||||||
|
*/
|
||||||
|
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
||||||
|
return [this.constructor as GenericConstructor<ListableObject>];
|
||||||
|
}
|
||||||
|
}
|
29
src/app/core/shared/external-source.model.ts
Normal file
29
src/app/core/shared/external-source.model.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for an external source
|
||||||
|
*/
|
||||||
|
export class ExternalSource extends CacheableObject {
|
||||||
|
static type = new ResourceType('externalsource');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifier
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this external source
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the source hierarchical?
|
||||||
|
*/
|
||||||
|
hierarchical: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The link to the rest endpoint where this External Source can be found
|
||||||
|
*/
|
||||||
|
self: string;
|
||||||
|
}
|
@@ -25,6 +25,7 @@ import { PersonInputSuggestionsComponent } from './submission/item-list-elements
|
|||||||
import { NameVariantModalComponent } from './submission/name-variant-modal/name-variant-modal.component';
|
import { NameVariantModalComponent } from './submission/name-variant-modal/name-variant-modal.component';
|
||||||
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
|
import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component';
|
||||||
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
|
import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component';
|
||||||
|
import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
OrgUnitComponent,
|
OrgUnitComponent,
|
||||||
@@ -48,7 +49,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
PersonInputSuggestionsComponent,
|
PersonInputSuggestionsComponent,
|
||||||
NameVariantModalComponent,
|
NameVariantModalComponent,
|
||||||
OrgUnitSearchResultListSubmissionElementComponent,
|
OrgUnitSearchResultListSubmissionElementComponent,
|
||||||
OrgUnitInputSuggestionsComponent
|
OrgUnitInputSuggestionsComponent,
|
||||||
|
ExternalSourceEntryListSubmissionElementComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
<div>{{object.display}}</div>
|
||||||
|
<div *ngIf="uri"><a target="_blank" [href]="uri.value">{{uri.value}}</a></div>
|
@@ -0,0 +1,47 @@
|
|||||||
|
import { ExternalSourceEntryListSubmissionElementComponent } from './external-source-entry-list-submission-element.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ExternalSourceEntry } from '../../../../../core/shared/external-source-entry.model';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('ExternalSourceEntryListSubmissionElementComponent', () => {
|
||||||
|
let component: ExternalSourceEntryListSubmissionElementComponent;
|
||||||
|
let fixture: ComponentFixture<ExternalSourceEntryListSubmissionElementComponent>;
|
||||||
|
|
||||||
|
const uri = 'https://orcid.org/0001-0001-0001-0001';
|
||||||
|
const entry = Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: uri
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ExternalSourceEntryListSubmissionElementComponent],
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ExternalSourceEntryListSubmissionElementComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.object = entry;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the entry\'s display value', () => {
|
||||||
|
expect(fixture.nativeElement.textContent).toContain(entry.display);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the entry\'s uri', () => {
|
||||||
|
expect(fixture.nativeElement.textContent).toContain(uri);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { AbstractListableElementComponent } from '../../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
|
import { ExternalSourceEntry } from '../../../../../core/shared/external-source-entry.model';
|
||||||
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Metadata } from '../../../../../core/shared/metadata.utils';
|
||||||
|
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
@listableObjectComponent(ExternalSourceEntry, ViewMode.ListElement, Context.SubmissionModal)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-external-source-entry-list-submission-element',
|
||||||
|
styleUrls: ['./external-source-entry-list-submission-element.component.scss'],
|
||||||
|
templateUrl: './external-source-entry-list-submission-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying a list element of an external source entry
|
||||||
|
*/
|
||||||
|
export class ExternalSourceEntryListSubmissionElementComponent extends AbstractListableElementComponent<ExternalSourceEntry> implements OnInit {
|
||||||
|
/**
|
||||||
|
* The metadata value for the object's uri
|
||||||
|
*/
|
||||||
|
uri: MetadataValue;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.uri = Metadata.first(this.object.metadata, 'dc.identifier.uri');
|
||||||
|
}
|
||||||
|
}
|
@@ -10,7 +10,13 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
templateUrl: './name-variant-modal.component.html',
|
templateUrl: './name-variant-modal.component.html',
|
||||||
styleUrls: ['./name-variant-modal.component.scss']
|
styleUrls: ['./name-variant-modal.component.scss']
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* The component for the modal to add a name variant to an item
|
||||||
|
*/
|
||||||
export class NameVariantModalComponent {
|
export class NameVariantModalComponent {
|
||||||
|
/**
|
||||||
|
* The name variant
|
||||||
|
*/
|
||||||
@Input() value: string;
|
@Input() value: string;
|
||||||
|
|
||||||
constructor(public modal: NgbActiveModal) {
|
constructor(public modal: NgbActiveModal) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ul class="navbar-nav" [ngClass]="{'mr-auto': (isXsOrSm$ | async)}">
|
<ul class="navbar-nav" [ngClass]="{'mr-auto': (isXsOrSm$ | async)}">
|
||||||
<li *ngIf="!(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item" (click)="$event.stopPropagation();">
|
<li *ngIf="!(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item" (click)="$event.stopPropagation();">
|
||||||
<div ngbDropdown placement="bottom-right" class="d-inline-block" @fadeInOut>
|
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||||
<a href="#" id="dropdownLogin" (click)="$event.preventDefault()" ngbDropdownToggle class="px-1">{{ 'nav.login' | translate }}</a>
|
<a href="#" id="dropdownLogin" (click)="$event.preventDefault()" ngbDropdownToggle class="px-1">{{ 'nav.login' | translate }}</a>
|
||||||
<div id="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu aria-labelledby="dropdownLogin">
|
<div id="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu aria-labelledby="dropdownLogin">
|
||||||
<ds-log-in
|
<ds-log-in
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<a id="loginLink" routerLink="/login" routerLinkActive="active" class="px-1" >{{ 'nav.login' | translate }}<span class="sr-only">(current)</span></a>
|
<a id="loginLink" routerLink="/login" routerLinkActive="active" class="px-1" >{{ 'nav.login' | translate }}<span class="sr-only">(current)</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item">
|
<li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item">
|
||||||
<div ngbDropdown placement="bottom-right" class="d-inline-block" @fadeInOut>
|
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||||
<a href="#" id="dropdownUser" (click)="$event.preventDefault()" class="px-1" ngbDropdownToggle><i class="fas fa-user-circle fa-lg fa-fw" [title]="'nav.logout' | translate"></i></a>
|
<a href="#" id="dropdownUser" (click)="$event.preventDefault()" class="px-1" ngbDropdownToggle><i class="fas fa-user-circle fa-lg fa-fw" [title]="'nav.logout' | translate"></i></a>
|
||||||
<div id="logoutDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownUser">
|
<div id="logoutDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownUser">
|
||||||
<ds-user-menu></ds-user-menu>
|
<ds-user-menu></ds-user-menu>
|
||||||
|
@@ -3,9 +3,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core';
|
import { DynamicFormControlModel, DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { ComColFormComponent } from './comcol-form.component';
|
import { ComColFormComponent } from './comcol-form.component';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
@@ -137,7 +136,7 @@ describe('ComColFormComponent', () => {
|
|||||||
type: Community.type
|
type: Community.type
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
uploader: {},
|
uploader: {} as any,
|
||||||
deleteLogo: false
|
deleteLogo: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core';
|
import {
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormService,
|
||||||
|
DynamicInputModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { ResourceType } from '../../../core/shared/resource-type';
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
@@ -186,7 +190,7 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit, OnDe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const formMetadata = new Object() as MetadataMap;
|
const formMetadata = {} as MetadataMap;
|
||||||
this.formModel.forEach((fieldModel: DynamicInputModel) => {
|
this.formModel.forEach((fieldModel: DynamicInputModel) => {
|
||||||
const value: MetadataValue = {
|
const value: MetadataValue = {
|
||||||
value: fieldModel.value as string,
|
value: fieldModel.value as string,
|
||||||
|
@@ -32,7 +32,7 @@ import {
|
|||||||
DynamicFormControl,
|
DynamicFormControl,
|
||||||
DynamicFormControlContainerComponent,
|
DynamicFormControlContainerComponent,
|
||||||
DynamicFormControlEvent,
|
DynamicFormControlEvent,
|
||||||
DynamicFormControlModel,
|
DynamicFormControlModel, DynamicFormInstancesService,
|
||||||
DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
DynamicFormLayoutService,
|
DynamicFormLayoutService,
|
||||||
DynamicFormValidationService,
|
DynamicFormValidationService,
|
||||||
@@ -213,6 +213,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected componentFactoryResolver: ComponentFactoryResolver,
|
protected componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
protected dynamicFormInstanceService: DynamicFormInstancesService,
|
||||||
protected layoutService: DynamicFormLayoutService,
|
protected layoutService: DynamicFormLayoutService,
|
||||||
protected validationService: DynamicFormValidationService,
|
protected validationService: DynamicFormValidationService,
|
||||||
protected translateService: TranslateService,
|
protected translateService: TranslateService,
|
||||||
@@ -225,7 +226,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
private submissionObjectService: SubmissionObjectDataService
|
private submissionObjectService: SubmissionObjectDataService
|
||||||
) {
|
) {
|
||||||
super(componentFactoryResolver, layoutService, validationService);
|
|
||||||
|
super(componentFactoryResolver, layoutService, validationService, dynamicFormInstanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
import { DynamicDateControlModel, DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core';
|
import {
|
||||||
import { DynamicDateControlModelConfig } from '@ng-dynamic-forms/core/src/model/dynamic-date-control.model';
|
DynamicDateControlModel,
|
||||||
|
DynamicDateControlModelConfig,
|
||||||
|
DynamicFormControlLayout,
|
||||||
|
serializable
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
export const DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER = 'DATE';
|
export const DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER = 'DATE';
|
||||||
|
@@ -11,6 +11,9 @@ import { DynamicDisabledModel } from './dynamic-disabled.model';
|
|||||||
selector: 'ds-dynamic-disabled',
|
selector: 'ds-dynamic-disabled',
|
||||||
templateUrl: './dynamic-disabled.component.html'
|
templateUrl: './dynamic-disabled.component.html'
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* Component for displaying a form input with a disabled property
|
||||||
|
*/
|
||||||
export class DsDynamicDisabledComponent extends DynamicFormControlComponent {
|
export class DsDynamicDisabledComponent extends DynamicFormControlComponent {
|
||||||
|
|
||||||
@Input() formId: string;
|
@Input() formId: string;
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
import { DynamicFormControlLayout, DynamicFormGroupModel, serializable } from '@ng-dynamic-forms/core';
|
import {
|
||||||
|
DynamicFormControlLayout,
|
||||||
|
DynamicFormGroupModel,
|
||||||
|
DynamicFormGroupModelConfig,
|
||||||
|
serializable
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
import { DsDynamicInputModel } from './ds-dynamic-input.model';
|
import { DsDynamicInputModel } from './ds-dynamic-input.model';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core/src/model/form-group/dynamic-form-group.model';
|
|
||||||
import { LanguageCode } from '../../models/form-field-language-value.model';
|
import { LanguageCode } from '../../models/form-field-language-value.model';
|
||||||
|
|
||||||
export const QUALDROP_GROUP_SUFFIX = '_QUALDROP_GROUP';
|
export const QUALDROP_GROUP_SUFFIX = '_QUALDROP_GROUP';
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ngb-tabset>
|
<ngb-tabset>
|
||||||
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + label | translate">
|
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + label | translate : {count: (totalInternal$ | async)}">
|
||||||
<ng-template ngbTabContent>
|
<ng-template ngbTabContent>
|
||||||
<ds-dynamic-lookup-relation-search-tab
|
<ds-dynamic-lookup-relation-search-tab
|
||||||
[selection$]="selection$"
|
[selection$]="selection$"
|
||||||
@@ -21,6 +21,20 @@
|
|||||||
</ds-dynamic-lookup-relation-search-tab>
|
</ds-dynamic-lookup-relation-search-tab>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</ngb-tab>
|
||||||
|
<ngb-tab *ngFor="let source of (externalSourcesRD$ | async)?.payload?.page; let idx = index"
|
||||||
|
[title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : {count: (totalExternal$ | async)[idx]}">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-dynamic-lookup-relation-external-source-tab
|
||||||
|
[listId]="listId"
|
||||||
|
[repeatable]="repeatable"
|
||||||
|
[context]="context"
|
||||||
|
[externalSource]="source"
|
||||||
|
(selectObject)="select($event)"
|
||||||
|
(deselectObject)="deselect($event)"
|
||||||
|
class="d-block pt-3">
|
||||||
|
</ds-dynamic-lookup-relation-external-source-tab>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : {count: (selection$ | async)?.length}">
|
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : {count: (selection$ | async)?.length}">
|
||||||
<ng-template ngbTabContent>
|
<ng-template ngbTabContent>
|
||||||
<ds-dynamic-lookup-relation-selection-tab
|
<ds-dynamic-lookup-relation-selection-tab
|
||||||
|
@@ -13,6 +13,12 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||||
import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.actions';
|
import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.actions';
|
||||||
|
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../../search/paginated-search-options.model';
|
||||||
|
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
||||||
|
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../../../testing/utils';
|
||||||
|
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
||||||
|
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
||||||
|
|
||||||
describe('DsDynamicLookupRelationModalComponent', () => {
|
describe('DsDynamicLookupRelationModalComponent', () => {
|
||||||
let component: DsDynamicLookupRelationModalComponent;
|
let component: DsDynamicLookupRelationModalComponent;
|
||||||
@@ -28,6 +34,24 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
let relationship;
|
let relationship;
|
||||||
let nameVariant;
|
let nameVariant;
|
||||||
let metadataField;
|
let metadataField;
|
||||||
|
let pSearchOptions;
|
||||||
|
let externalSourceService;
|
||||||
|
let lookupRelationService;
|
||||||
|
|
||||||
|
const externalSources = [
|
||||||
|
Object.assign(new ExternalSource(), {
|
||||||
|
id: 'orcidV2',
|
||||||
|
name: 'orcidV2',
|
||||||
|
hierarchical: false
|
||||||
|
}),
|
||||||
|
Object.assign(new ExternalSource(), {
|
||||||
|
id: 'sherpaPublisher',
|
||||||
|
name: 'sherpaPublisher',
|
||||||
|
hierarchical: false
|
||||||
|
})
|
||||||
|
];
|
||||||
|
const totalLocal = 10;
|
||||||
|
const totalExternal = 8;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
|
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
|
||||||
@@ -41,6 +65,14 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
|
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
|
||||||
nameVariant = 'Doe, J.';
|
nameVariant = 'Doe, J.';
|
||||||
metadataField = 'dc.contributor.author';
|
metadataField = 'dc.contributor.author';
|
||||||
|
pSearchOptions = new PaginatedSearchOptions({});
|
||||||
|
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
||||||
|
findAll: createSuccessfulRemoteDataObject$(createPaginatedList(externalSources))
|
||||||
|
});
|
||||||
|
lookupRelationService = jasmine.createSpyObj('lookupRelationService', {
|
||||||
|
getTotalLocalResults: observableOf(totalLocal),
|
||||||
|
getTotalExternalResults: observableOf(totalExternal)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@@ -49,6 +81,13 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
declarations: [DsDynamicLookupRelationModalComponent],
|
declarations: [DsDynamicLookupRelationModalComponent],
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule.forRoot()],
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule.forRoot()],
|
||||||
providers: [
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SearchConfigurationService, useValue: {
|
||||||
|
paginatedSearchOptions: observableOf(pSearchOptions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ provide: ExternalSourceService, useValue: externalSourceService },
|
||||||
|
{ provide: LookupRelationService, useValue: lookupRelationService },
|
||||||
{
|
{
|
||||||
provide: SelectableListService, useValue: selectableListService
|
provide: SelectableListService, useValue: selectableListService
|
||||||
},
|
},
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { combineLatest, Observable, Subscription } from 'rxjs';
|
import { combineLatest, Observable, Subscription, zip as observableZip } from 'rxjs';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { hasValue } from '../../../../empty.util';
|
import { hasValue } from '../../../../empty.util';
|
||||||
import { map, skip, switchMap, take } from 'rxjs/operators';
|
import { map, skip, switchMap, take } from 'rxjs/operators';
|
||||||
@@ -11,7 +11,11 @@ import { ListableObject } from '../../../../object-collection/shared/listable-ob
|
|||||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||||
import { SearchResult } from '../../../../search/search-result.model';
|
import { SearchResult } from '../../../../search/search-result.model';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../core/shared/operators';
|
import {
|
||||||
|
getAllSucceededRemoteData,
|
||||||
|
getRemoteDataPayload,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../../../../../core/shared/operators';
|
||||||
import { AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipAction } from './relationship.actions';
|
import { AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipAction } from './relationship.actions';
|
||||||
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
||||||
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
||||||
@@ -20,6 +24,11 @@ import { AppState } from '../../../../../app.reducer';
|
|||||||
import { Context } from '../../../../../core/shared/context.model';
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||||
|
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
|
import { ExternalSource } from '../../../../../core/shared/external-source.model';
|
||||||
|
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-modal',
|
selector: 'ds-dynamic-lookup-relation-modal',
|
||||||
@@ -37,23 +46,76 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
|||||||
* Represents a modal where the submitter can select items to be added as a certain relationship type to the object being submitted
|
* Represents a modal where the submitter can select items to be added as a certain relationship type to the object being submitted
|
||||||
*/
|
*/
|
||||||
export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy {
|
export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* The label to use to display i18n messages (describing the type of relationship)
|
||||||
|
*/
|
||||||
label: string;
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for searching related items
|
||||||
|
*/
|
||||||
relationshipOptions: RelationshipOptions;
|
relationshipOptions: RelationshipOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the list to add/remove selected items to/from
|
||||||
|
*/
|
||||||
listId: string;
|
listId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item we're adding relationships to
|
||||||
|
*/
|
||||||
item;
|
item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the selection repeatable?
|
||||||
|
*/
|
||||||
repeatable: boolean;
|
repeatable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of selected items
|
||||||
|
*/
|
||||||
selection$: Observable<ListableObject[]>;
|
selection$: Observable<ListableObject[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context to display lists
|
||||||
|
*/
|
||||||
context: Context;
|
context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata-fields describing these relationships
|
||||||
|
*/
|
||||||
metadataFields: string;
|
metadataFields: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of subscriptions within this component
|
||||||
|
*/
|
||||||
subMap: {
|
subMap: {
|
||||||
[uuid: string]: Subscription
|
[uuid: string]: Subscription
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the available external sources configured for this relationship
|
||||||
|
*/
|
||||||
|
externalSourcesRD$: Observable<RemoteData<PaginatedList<ExternalSource>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total amount of internal items for the current options
|
||||||
|
*/
|
||||||
|
totalInternal$: Observable<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total amount of results for each external source using the current options
|
||||||
|
*/
|
||||||
|
totalExternal$: Observable<number[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public modal: NgbActiveModal,
|
public modal: NgbActiveModal,
|
||||||
private selectableListService: SelectableListService,
|
private selectableListService: SelectableListService,
|
||||||
private relationshipService: RelationshipService,
|
private relationshipService: RelationshipService,
|
||||||
private relationshipTypeService: RelationshipTypeService,
|
private relationshipTypeService: RelationshipTypeService,
|
||||||
|
private externalSourceService: ExternalSourceService,
|
||||||
|
private lookupRelationService: LookupRelationService,
|
||||||
|
private searchConfigService: SearchConfigurationService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private store: Store<AppState>
|
private store: Store<AppState>
|
||||||
) {
|
) {
|
||||||
@@ -70,6 +132,9 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
this.context = Context.SubmissionModal;
|
this.context = Context.SubmissionModal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.externalSourcesRD$ = this.externalSourceService.findAll();
|
||||||
|
|
||||||
|
this.setTotals();
|
||||||
// this.setExistingNameVariants();
|
// this.setExistingNameVariants();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +142,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
this.modal.close();
|
this.modal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select (a list of) objects and add them to the store
|
||||||
|
* @param selectableObjects
|
||||||
|
*/
|
||||||
select(...selectableObjects: Array<SearchResult<Item>>) {
|
select(...selectableObjects: Array<SearchResult<Item>>) {
|
||||||
this.zone.runOutsideAngular(
|
this.zone.runOutsideAngular(
|
||||||
() => {
|
() => {
|
||||||
@@ -104,6 +173,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a subscription updating relationships with name variants
|
||||||
|
* @param sri The search result to track name variants for
|
||||||
|
*/
|
||||||
private addNameVariantSubscription(sri: SearchResult<Item>) {
|
private addNameVariantSubscription(sri: SearchResult<Item>) {
|
||||||
const nameVariant$ = this.relationshipService.getNameVariant(this.listId, sri.indexableObject.uuid);
|
const nameVariant$ = this.relationshipService.getNameVariant(this.listId, sri.indexableObject.uuid);
|
||||||
this.subMap[sri.indexableObject.uuid] = nameVariant$.pipe(
|
this.subMap[sri.indexableObject.uuid] = nameVariant$.pipe(
|
||||||
@@ -111,6 +184,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
).subscribe((nameVariant: string) => this.store.dispatch(new UpdateRelationshipAction(this.item, sri.indexableObject, this.relationshipOptions.relationshipType, nameVariant)))
|
).subscribe((nameVariant: string) => this.store.dispatch(new UpdateRelationshipAction(this.item, sri.indexableObject, this.relationshipOptions.relationshipType, nameVariant)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect (a list of) objects and remove them from the store
|
||||||
|
* @param selectableObjects
|
||||||
|
*/
|
||||||
deselect(...selectableObjects: Array<SearchResult<Item>>) {
|
deselect(...selectableObjects: Array<SearchResult<Item>>) {
|
||||||
this.zone.runOutsideAngular(
|
this.zone.runOutsideAngular(
|
||||||
() => selectableObjects.forEach((object) => {
|
() => selectableObjects.forEach((object) => {
|
||||||
@@ -120,6 +197,9 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set existing name variants for items by the item's virtual metadata
|
||||||
|
*/
|
||||||
private setExistingNameVariants() {
|
private setExistingNameVariants() {
|
||||||
const virtualMDs: MetadataValue[] = this.item.allMetadata(this.metadataFields).filter((mdValue) => mdValue.isVirtual);
|
const virtualMDs: MetadataValue[] = this.item.allMetadata(this.metadataFields).filter((mdValue) => mdValue.isVirtual);
|
||||||
|
|
||||||
@@ -136,7 +216,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
);
|
);
|
||||||
|
|
||||||
const relatedItems$: Observable<Item[]> = relatedItemPairs$.pipe(
|
const relatedItems$: Observable<Item[]> = relatedItemPairs$.pipe(
|
||||||
map(([relatedItemPairs,]: [Array<[Item, Item]>]) => relatedItemPairs.map(([left, right]: [Item, Item]) => left.uuid === this.item.uuid ? left : right))
|
map((relatedItemPairs: Array<[Item, Item]>) => {
|
||||||
|
return relatedItemPairs
|
||||||
|
.map(([left, right]: [Item, Item]) => left.uuid === this.item.uuid ? left : right)
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
relatedItems$.pipe(take(1)).subscribe((relatedItems) => {
|
relatedItems$.pipe(take(1)).subscribe((relatedItems) => {
|
||||||
@@ -151,6 +234,28 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and set the total entries available for each tab
|
||||||
|
*/
|
||||||
|
setTotals() {
|
||||||
|
this.totalInternal$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
|
switchMap((options) => this.lookupRelationService.getTotalLocalResults(this.relationshipOptions, options))
|
||||||
|
);
|
||||||
|
|
||||||
|
const externalSourcesAndOptions$ = combineLatest(
|
||||||
|
this.externalSourcesRD$.pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
),
|
||||||
|
this.searchConfigService.paginatedSearchOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
this.totalExternal$ = externalSourcesAndOptions$.pipe(
|
||||||
|
switchMap(([sources, options]) =>
|
||||||
|
observableZip(...sources.page.map((source: ExternalSource) => this.lookupRelationService.getTotalExternalResults(source, options))))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
|
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.settings' | translate}}</h3>
|
||||||
|
<ds-page-size-selector></ds-page-size-selector>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<ds-search-form [query]="(searchConfigService.paginatedSearchOptions | async)?.query" [inPlaceSearch]="true"></ds-search-form>
|
||||||
|
<div>
|
||||||
|
<h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + externalSource.id | translate}}</h3>
|
||||||
|
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
|
||||||
|
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !entriesRD?.isLoading && entriesRD?.payload?.page?.length > 0" @fadeIn
|
||||||
|
[objects]="entriesRD"
|
||||||
|
[selectable]="true"
|
||||||
|
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
||||||
|
[config]="initialPagination"
|
||||||
|
[hideGear]="true"
|
||||||
|
[context]="context"
|
||||||
|
(deselectObject)="deselectObject.emit($event)"
|
||||||
|
(selectObject)="selectObject.emit($event)">
|
||||||
|
</ds-viewable-collection>
|
||||||
|
<ds-loading *ngIf="!entriesRD || !entriesRD?.payload || entriesRD?.isLoading"
|
||||||
|
message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||||
|
<ds-error *ngIf="entriesRD?.hasFailed && (!entriesRD?.error || entriesRD?.error?.statusCode != 400)"
|
||||||
|
message="{{'error.search-results' | translate}}"></ds-error>
|
||||||
|
<div *ngIf="entriesRD?.payload?.page?.length == 0 || entriesRD?.error?.statusCode == 400" id="empty-external-entry-list">
|
||||||
|
{{ 'search.results.empty' | translate }}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,162 @@
|
|||||||
|
import { DsDynamicLookupRelationExternalSourceTabComponent } from './dynamic-lookup-relation-external-source-tab.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { VarDirective } from '../../../../../utils/var.directive';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
||||||
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject$,
|
||||||
|
createPaginatedList,
|
||||||
|
createPendingRemoteDataObject$,
|
||||||
|
createSuccessfulRemoteDataObject$
|
||||||
|
} from '../../../../../testing/utils';
|
||||||
|
import { ExternalSourceService } from '../../../../../../core/data/external-source.service';
|
||||||
|
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
||||||
|
|
||||||
|
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
||||||
|
let component: DsDynamicLookupRelationExternalSourceTabComponent;
|
||||||
|
let fixture: ComponentFixture<DsDynamicLookupRelationExternalSourceTabComponent>;
|
||||||
|
let pSearchOptions;
|
||||||
|
let externalSourceService;
|
||||||
|
|
||||||
|
const externalSource = {
|
||||||
|
id: 'orcidV2',
|
||||||
|
name: 'orcidV2',
|
||||||
|
hierarchical: false
|
||||||
|
} as ExternalSource;
|
||||||
|
const externalEntries = [
|
||||||
|
Object.assign({
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0001'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign({
|
||||||
|
id: '0001-0001-0001-0002',
|
||||||
|
display: 'Sampson Megan',
|
||||||
|
value: 'Sampson, Megan',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0002'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Object.assign({
|
||||||
|
id: '0001-0001-0001-0003',
|
||||||
|
display: 'Edwards Anna',
|
||||||
|
value: 'Edwards, Anna',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0003'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
] as ExternalSourceEntry[];
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
pSearchOptions = new PaginatedSearchOptions({
|
||||||
|
query: 'test'
|
||||||
|
});
|
||||||
|
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
||||||
|
getExternalSourceEntries: createSuccessfulRemoteDataObject$(createPaginatedList(externalEntries))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [DsDynamicLookupRelationExternalSourceTabComponent, VarDirective],
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), BrowserAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SearchConfigurationService, useValue: {
|
||||||
|
paginatedSearchOptions: observableOf(pSearchOptions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ provide: ExternalSourceService, useValue: externalSourceService }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DsDynamicLookupRelationExternalSourceTabComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.externalSource = externalSource;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the external entries finished loading successfully', () => {
|
||||||
|
it('should display a ds-viewable-collection component', () => {
|
||||||
|
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
|
expect(collection).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the external entries are loading', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.entriesRD$ = createPendingRemoteDataObject$(undefined);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
|
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
|
expect(collection).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a ds-loading component', () => {
|
||||||
|
const loading = fixture.debugElement.query(By.css('ds-loading'));
|
||||||
|
expect(loading).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the external entries failed loading', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.entriesRD$ = createFailedRemoteDataObject$(undefined);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
|
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
|
expect(collection).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a ds-error component', () => {
|
||||||
|
const error = fixture.debugElement.query(By.css('ds-error'));
|
||||||
|
expect(error).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the external entries return an empty list', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.entriesRD$ = createSuccessfulRemoteDataObject$(createPaginatedList([]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
|
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
|
expect(collection).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a message the list is empty', () => {
|
||||||
|
const empty = fixture.debugElement.query(By.css('#empty-external-entry-list'));
|
||||||
|
expect(empty).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,96 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
||||||
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ExternalSourceService } from '../../../../../../core/data/external-source.service';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../../../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||||
|
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
||||||
|
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
||||||
|
import { startWith, switchMap } from 'rxjs/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
||||||
|
import { Context } from '../../../../../../core/shared/context.model';
|
||||||
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
|
import { fadeIn, fadeInOut } from '../../../../../animations/fade';
|
||||||
|
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-dynamic-lookup-relation-external-source-tab',
|
||||||
|
styleUrls: ['./dynamic-lookup-relation-external-source-tab.component.scss'],
|
||||||
|
templateUrl: './dynamic-lookup-relation-external-source-tab.component.html',
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
|
useClass: SearchConfigurationService
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animations: [
|
||||||
|
fadeIn,
|
||||||
|
fadeInOut
|
||||||
|
]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The tab displaying a list of importable entries for an external source
|
||||||
|
*/
|
||||||
|
export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The label to use to display i18n messages (describing the type of relationship)
|
||||||
|
*/
|
||||||
|
@Input() label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the list to add/remove selected items to/from
|
||||||
|
*/
|
||||||
|
@Input() listId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the selection repeatable?
|
||||||
|
*/
|
||||||
|
@Input() repeatable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context to display lists
|
||||||
|
*/
|
||||||
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to deselect an object from the list
|
||||||
|
*/
|
||||||
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to select an object from the list
|
||||||
|
*/
|
||||||
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial pagination to start with
|
||||||
|
*/
|
||||||
|
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'submission-external-source-relation-list',
|
||||||
|
pageSize: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external source we're selecting entries for
|
||||||
|
*/
|
||||||
|
@Input() externalSource: ExternalSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The displayed list of entries
|
||||||
|
*/
|
||||||
|
entriesRD$: Observable<RemoteData<PaginatedList<ExternalSourceEntry>>>;
|
||||||
|
|
||||||
|
constructor(private router: Router,
|
||||||
|
public searchConfigService: SearchConfigurationService,
|
||||||
|
private externalSourceService: ExternalSourceService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
|
switchMap((searchOptions: PaginatedSearchOptions) =>
|
||||||
|
this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||||
[inPlaceSearch]="true" [showViewModes]="false"></ds-search-sidebar>
|
[inPlaceSearch]="true" [showViewModes]="false"></ds-search-sidebar>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<ds-search-form [inPlaceSearch]="true"></ds-search-form>
|
<ds-search-form [query]="(searchConfigService.paginatedSearchOptions | async)?.query" [inPlaceSearch]="true"></ds-search-form>
|
||||||
|
|
||||||
<ds-search-labels [inPlaceSearch]="true"></ds-search-labels>
|
<ds-search-labels [inPlaceSearch]="true"></ds-search-labels>
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="(resultsRD$ | async)"
|
<ds-search-results [searchResults]="(resultsRD$ | async)"
|
||||||
[sortConfig]="this.searchConfig?.sort"
|
[sortConfig]="this.lookupRelationService.searchConfig?.sort"
|
||||||
[searchConfig]="this.searchConfig"
|
[searchConfig]="this.lookupRelationService.searchConfig"
|
||||||
[selectable]="true"
|
[selectable]="true"
|
||||||
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
||||||
[linkType]="linkTypes.ExternalLink"
|
[linkType]="linkTypes.ExternalLink"
|
||||||
|
@@ -15,6 +15,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../testing/utils'
|
|||||||
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../../../core/data/paginated-list';
|
||||||
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
|
||||||
import { Item } from '../../../../../../core/shared/item.model';
|
import { Item } from '../../../../../../core/shared/item.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { LookupRelationService } from '../../../../../../core/data/lookup-relation.service';
|
||||||
|
|
||||||
describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
||||||
let component: DsDynamicLookupRelationSearchTabComponent;
|
let component: DsDynamicLookupRelationSearchTabComponent;
|
||||||
@@ -34,6 +36,7 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
|
|
||||||
let results;
|
let results;
|
||||||
let selectableListService;
|
let selectableListService;
|
||||||
|
let lookupRelationService;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
|
relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
|
||||||
@@ -51,6 +54,10 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
|
|
||||||
results = new PaginatedList(undefined, [searchResult1, searchResult2, searchResult3]);
|
results = new PaginatedList(undefined, [searchResult1, searchResult2, searchResult3]);
|
||||||
selectableListService = jasmine.createSpyObj('selectableListService', ['deselect', 'select', 'deselectAll']);
|
selectableListService = jasmine.createSpyObj('selectableListService', ['deselect', 'select', 'deselectAll']);
|
||||||
|
lookupRelationService = jasmine.createSpyObj('lookupRelationService', {
|
||||||
|
getLocalResults: createSuccessfulRemoteDataObject$(results)
|
||||||
|
});
|
||||||
|
lookupRelationService.searchConfig = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@@ -75,6 +82,8 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ provide: ActivatedRoute, useValue: { snapshot: { queryParams: {} } } },
|
||||||
|
{ provide: LookupRelationService, useValue: lookupRelationService }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
@@ -113,7 +122,7 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
|
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
|
||||||
expect(component.deselectObject.emit).toHaveBeenCalledWith(searchResult1, searchResult2);
|
expect((component.deselectObject as any).emit).toHaveBeenCalledWith(searchResult1, searchResult2);
|
||||||
expect(selectableListService.deselect).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
|
expect(selectableListService.deselect).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -137,7 +146,7 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
|
it('should emit the page filtered from not yet selected objects and call select on the service for all objects', () => {
|
||||||
expect(component.deselectObject.emit).toHaveBeenCalledWith(searchResult1, searchResult2);
|
expect((component.deselectObject as any).emit).toHaveBeenCalledWith(searchResult1, searchResult2);
|
||||||
expect(selectableListService.deselectAll).toHaveBeenCalledWith(listID);
|
expect(selectableListService.deselectAll).toHaveBeenCalledWith(listID);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ import { RelationshipOptions } from '../../../models/relationship-options.model'
|
|||||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
||||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
import { SearchService } from '../../../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../../../core/shared/search/search.service';
|
||||||
import { Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||||
import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
|
import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
|
||||||
@@ -20,6 +20,7 @@ import { getSucceededRemoteData } from '../../../../../../core/shared/operators'
|
|||||||
import { RouteService } from '../../../../../../core/services/route.service';
|
import { RouteService } from '../../../../../../core/services/route.service';
|
||||||
import { CollectionElementLinkType } from '../../../../../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../../../../../object-collection/collection-element-link.type';
|
||||||
import { Context } from '../../../../../../core/shared/context.model';
|
import { Context } from '../../../../../../core/shared/context.model';
|
||||||
|
import { LookupRelationService } from '../../../../../../core/data/lookup-relation.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-search-tab',
|
selector: 'ds-dynamic-lookup-relation-search-tab',
|
||||||
@@ -37,32 +38,87 @@ import { Context } from '../../../../../../core/shared/context.model';
|
|||||||
* Tab for inside the lookup model that represents the items that can be used as a relationship in this submission
|
* Tab for inside the lookup model that represents the items that can be used as a relationship in this submission
|
||||||
*/
|
*/
|
||||||
export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
|
export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* Options for searching related items
|
||||||
|
*/
|
||||||
@Input() relationship: RelationshipOptions;
|
@Input() relationship: RelationshipOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the list to add/remove selected items to/from
|
||||||
|
*/
|
||||||
@Input() listId: string;
|
@Input() listId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the selection repeatable?
|
||||||
|
*/
|
||||||
@Input() repeatable: boolean;
|
@Input() repeatable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of selected items
|
||||||
|
*/
|
||||||
@Input() selection$: Observable<ListableObject[]>;
|
@Input() selection$: Observable<ListableObject[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context to display lists
|
||||||
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to deselect an object from the list
|
||||||
|
*/
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to select an object from the list
|
||||||
|
*/
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search results
|
||||||
|
*/
|
||||||
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
||||||
searchConfig: PaginatedSearchOptions;
|
|
||||||
|
/**
|
||||||
|
* Are all results selected?
|
||||||
|
*/
|
||||||
allSelected: boolean;
|
allSelected: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are some results selected?
|
||||||
|
*/
|
||||||
someSelected$: Observable<boolean>;
|
someSelected$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is it currently loading to select all results?
|
||||||
|
*/
|
||||||
selectAllLoading: boolean;
|
selectAllLoading: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to unsubscribe from
|
||||||
|
*/
|
||||||
subscription;
|
subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial pagination to use
|
||||||
|
*/
|
||||||
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'submission-relation-list',
|
id: 'submission-relation-list',
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of links to display
|
||||||
|
*/
|
||||||
linkTypes = CollectionElementLinkType;
|
linkTypes = CollectionElementLinkType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
private selectableListService: SelectableListService,
|
private selectableListService: SelectableListService,
|
||||||
private searchConfigService: SearchConfigurationService,
|
public searchConfigService: SearchConfigurationService,
|
||||||
private routeService: RouteService,
|
private routeService: RouteService,
|
||||||
|
public lookupRelationService: LookupRelationService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,23 +132,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
|
|
||||||
this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
|
this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
|
||||||
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
map((options) => {
|
switchMap((options) => this.lookupRelationService.getLocalResults(this.relationship, options))
|
||||||
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.relationship.filter, configuration: this.relationship.searchConfiguration })
|
|
||||||
}),
|
|
||||||
switchMap((options) => {
|
|
||||||
this.searchConfig = options;
|
|
||||||
return this.searchService.search(options).pipe(
|
|
||||||
/* Make sure to only listen to the first x results, until loading is finished */
|
|
||||||
/* TODO: in Rxjs 6.4.0 and up, we can replace this with takeWhile(predicate, true) - see https://stackoverflow.com/a/44644237 */
|
|
||||||
multicast(
|
|
||||||
() => new ReplaySubject(1),
|
|
||||||
(subject) => subject.pipe(
|
|
||||||
takeWhile((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => rd.isLoading),
|
|
||||||
concat(subject.pipe(take(1)))
|
|
||||||
)
|
|
||||||
) as any
|
|
||||||
)
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +141,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
*/
|
*/
|
||||||
resetRoute() {
|
resetRoute() {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
queryParams: Object.assign({}, { pageSize: this.initialPagination.pageSize }, this.route.snapshot.queryParams, { page: 1 })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +184,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 9999
|
pageSize: 9999
|
||||||
});
|
});
|
||||||
const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
|
const fullSearchConfig = Object.assign(this.lookupRelationService.searchConfig, { pagination: fullPagination });
|
||||||
const results$ = this.searchService.search(fullSearchConfig) as Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
const results$ = this.searchService.search(fullSearchConfig) as Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
||||||
results$.pipe(
|
results$.pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
|
@@ -73,11 +73,6 @@ describe('DsDynamicLookupRelationSelectionTabComponent', () => {
|
|||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call navigate on the router when is called resetRoute', () => {
|
|
||||||
component.resetRoute();
|
|
||||||
expect(router.navigate).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call navigate on the router when is called resetRoute', () => {
|
it('should call navigate on the router when is called resetRoute', () => {
|
||||||
component.selectionRD$ = createSelection([]);
|
component.selectionRD$ = createSelection([]);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -29,15 +29,49 @@ import { Context } from '../../../../../../core/shared/context.model';
|
|||||||
* Tab for inside the lookup model that represents the currently selected relationships
|
* Tab for inside the lookup model that represents the currently selected relationships
|
||||||
*/
|
*/
|
||||||
export class DsDynamicLookupRelationSelectionTabComponent {
|
export class DsDynamicLookupRelationSelectionTabComponent {
|
||||||
|
/**
|
||||||
|
* The label to use to display i18n messages (describing the type of relationship)
|
||||||
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the list to add/remove selected items to/from
|
||||||
|
*/
|
||||||
@Input() listId: string;
|
@Input() listId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the selection repeatable?
|
||||||
|
*/
|
||||||
@Input() repeatable: boolean;
|
@Input() repeatable: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of selected items
|
||||||
|
*/
|
||||||
@Input() selection$: Observable<ListableObject[]>;
|
@Input() selection$: Observable<ListableObject[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The paginated list of selected items
|
||||||
|
*/
|
||||||
@Input() selectionRD$: Observable<RemoteData<PaginatedList<ListableObject>>>;
|
@Input() selectionRD$: Observable<RemoteData<PaginatedList<ListableObject>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context to display lists
|
||||||
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to deselect an object from the list
|
||||||
|
*/
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to select an object from the list
|
||||||
|
*/
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial pagination to use
|
||||||
|
*/
|
||||||
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'submission-relation-list',
|
id: 'submission-relation-list',
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
@@ -51,7 +85,6 @@ export class DsDynamicLookupRelationSelectionTabComponent {
|
|||||||
* Set up the selection and pagination on load
|
* Set up the selection and pagination on load
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.resetRoute();
|
|
||||||
this.selectionRD$ = this.searchConfigService.paginatedSearchOptions
|
this.selectionRD$ = this.searchConfigService.paginatedSearchOptions
|
||||||
.pipe(
|
.pipe(
|
||||||
map((options: PaginatedSearchOptions) => options.pagination),
|
map((options: PaginatedSearchOptions) => options.pagination),
|
||||||
@@ -75,13 +108,4 @@ export class DsDynamicLookupRelationSelectionTabComponent {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to reset the route when the window is opened to make sure no strange pagination issues appears
|
|
||||||
*/
|
|
||||||
resetRoute() {
|
|
||||||
this.router.navigate([], {
|
|
||||||
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -10,10 +10,8 @@ import {
|
|||||||
DynamicFormArrayModel,
|
DynamicFormArrayModel,
|
||||||
DynamicFormControlModel,
|
DynamicFormControlModel,
|
||||||
DynamicFormGroupModel,
|
DynamicFormGroupModel,
|
||||||
DynamicFormService,
|
DynamicFormService, DynamicFormValidationService,
|
||||||
DynamicFormValidationService,
|
DynamicPathable, parseReviver,
|
||||||
DynamicPathable,
|
|
||||||
JSONUtils,
|
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { isObject, isString, mergeWith } from 'lodash';
|
import { isObject, isString, mergeWith } from 'lodash';
|
||||||
|
|
||||||
@@ -205,7 +203,7 @@ export class FormBuilderService extends DynamicFormService {
|
|||||||
|
|
||||||
modelFromConfiguration(submissionId: string, json: string | SubmissionFormsModel, scopeUUID: string, sectionData: any = {}, submissionScope?: string, readOnly = false): DynamicFormControlModel[] | never {
|
modelFromConfiguration(submissionId: string, json: string | SubmissionFormsModel, scopeUUID: string, sectionData: any = {}, submissionScope?: string, readOnly = false): DynamicFormControlModel[] | never {
|
||||||
let rows: DynamicFormControlModel[] = [];
|
let rows: DynamicFormControlModel[] = [];
|
||||||
const rawData = typeof json === 'string' ? JSON.parse(json, JSONUtils.parseReviver) : json;
|
const rawData = typeof json === 'string' ? JSON.parse(json, parseReviver) : json;
|
||||||
|
|
||||||
if (rawData.rows && !isEmpty(rawData.rows)) {
|
if (rawData.rows && !isEmpty(rawData.rows)) {
|
||||||
rawData.rows.forEach((currentRow) => {
|
rawData.rows.forEach((currentRow) => {
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
import { AppState } from '../../app.reducer';
|
|
||||||
|
|
||||||
export const historySelector = (state: AppState) => state.history;
|
|
@@ -1,40 +1,43 @@
|
|||||||
import * as ngrx from '@ngrx/store';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { async, TestBed } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { MenuService } from './menu.service';
|
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
|
||||||
import { MenuID } from './initial-menus-state';
|
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import * as ngrx from '@ngrx/store';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { MenuService } from './menu.service';
|
||||||
|
import { MenuID } from './initial-menus-state';
|
||||||
import {
|
import {
|
||||||
ActivateMenuSectionAction,
|
ActivateMenuSectionAction,
|
||||||
AddMenuSectionAction,
|
AddMenuSectionAction,
|
||||||
CollapseMenuAction, CollapseMenuPreviewAction, DeactivateMenuSectionAction,
|
CollapseMenuAction,
|
||||||
ExpandMenuAction, ExpandMenuPreviewAction, HideMenuAction,
|
CollapseMenuPreviewAction,
|
||||||
RemoveMenuSectionAction, ShowMenuAction, ToggleActiveMenuSectionAction, ToggleMenuAction
|
DeactivateMenuSectionAction,
|
||||||
|
ExpandMenuAction,
|
||||||
|
ExpandMenuPreviewAction,
|
||||||
|
HideMenuAction,
|
||||||
|
RemoveMenuSectionAction,
|
||||||
|
ShowMenuAction,
|
||||||
|
ToggleActiveMenuSectionAction,
|
||||||
|
ToggleMenuAction
|
||||||
} from './menu.actions';
|
} from './menu.actions';
|
||||||
|
import { MenuSection, menusReducer } from './menu.reducer';
|
||||||
|
|
||||||
describe('MenuService', () => {
|
describe('MenuService', () => {
|
||||||
let service: MenuService;
|
let service: MenuService;
|
||||||
let selectSpy;
|
let selectSpy;
|
||||||
let store;
|
let store: any;
|
||||||
let fakeMenu;
|
let fakeMenu;
|
||||||
let visibleSection1;
|
let visibleSection1;
|
||||||
let visibleSection2;
|
let visibleSection2;
|
||||||
let hiddenSection3;
|
let hiddenSection3;
|
||||||
let subSection4;
|
let subSection4;
|
||||||
let topSections;
|
let topSections;
|
||||||
|
let initialState;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
store = Object.assign(observableOf({}), {
|
|
||||||
dispatch: () => {/***/
|
|
||||||
}
|
|
||||||
}) as any;
|
|
||||||
fakeMenu = {
|
|
||||||
id: MenuID.ADMIN,
|
|
||||||
collapsed: true,
|
|
||||||
visible: false,
|
|
||||||
previewCollapsed: true
|
|
||||||
} as any;
|
|
||||||
visibleSection1 = {
|
visibleSection1 = {
|
||||||
id: 'section',
|
id: 'section',
|
||||||
visible: true,
|
visible: true,
|
||||||
@@ -60,35 +63,44 @@ describe('MenuService', () => {
|
|||||||
section_3: hiddenSection3,
|
section_3: hiddenSection3,
|
||||||
section_4: subSection4
|
section_4: subSection4
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fakeMenu = {
|
||||||
|
id: MenuID.ADMIN,
|
||||||
|
collapsed: true,
|
||||||
|
visible: false,
|
||||||
|
sections: topSections,
|
||||||
|
previewCollapsed: true
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
initialState = {
|
||||||
|
menus: {
|
||||||
|
'admin-sidebar' : fakeMenu
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StoreModule.forRoot({ menus: menusReducer })
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: Store, useValue: store },
|
provideMockStore({ initialState }),
|
||||||
{ provide: MenuService, useValue: service }
|
{ provide: MenuService, useValue: service }
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
store = TestBed.get(Store);
|
||||||
service = new MenuService(store);
|
service = new MenuService(store);
|
||||||
selectSpy = spyOnProperty(ngrx, 'select');
|
selectSpy = spyOnProperty(ngrx, 'select').and.callThrough();
|
||||||
spyOn(store, 'dispatch');
|
spyOn(store, 'dispatch');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMenu', () => {
|
describe('getMenu', () => {
|
||||||
beforeEach(() => {
|
|
||||||
selectSpy.and.callFake(() => {
|
|
||||||
return () => {
|
|
||||||
return () => hot('a', {
|
|
||||||
a: fakeMenu
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should return the menu', () => {
|
it('should return the menu', () => {
|
||||||
|
|
||||||
const result = service.getMenu(MenuID.ADMIN);
|
const result = service.getMenu(MenuID.ADMIN);
|
||||||
@@ -136,7 +148,7 @@ describe('MenuService', () => {
|
|||||||
describe('when the subsection list is not empty', () => {
|
describe('when the subsection list is not empty', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1));
|
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1 as MenuSection));
|
||||||
selectSpy.and.callFake(() => {
|
selectSpy.and.callFake(() => {
|
||||||
return () => {
|
return () => {
|
||||||
return () => hot('a', {
|
return () => hot('a', {
|
||||||
@@ -225,25 +237,25 @@ describe('MenuService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getMenuSection', () => {
|
describe('getMenuSection', () => {
|
||||||
beforeEach(() => {
|
it('should return menu section', () => {
|
||||||
selectSpy.and.callFake(() => {
|
|
||||||
return () => {
|
|
||||||
return () => hot('a', {
|
|
||||||
a: hiddenSection3
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should return false', () => {
|
|
||||||
|
|
||||||
const result = service.getMenuSection(MenuID.ADMIN, 'fakeId');
|
const result = service.getMenuSection(MenuID.ADMIN, 'section_3');
|
||||||
const expected = cold('b', {
|
const expected = cold('b', {
|
||||||
b: hiddenSection3
|
b: hiddenSection3
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return undefined', () => {
|
||||||
|
|
||||||
|
const result = service.getMenuSection(MenuID.ADMIN, 'fake');
|
||||||
|
const expected = cold('b', {
|
||||||
|
b: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isMenuCollapsed', () => {
|
describe('isMenuCollapsed', () => {
|
||||||
@@ -294,7 +306,7 @@ describe('MenuService', () => {
|
|||||||
|
|
||||||
describe('isSectionActive', () => {
|
describe('isSectionActive', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1));
|
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1 as MenuSection));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when the section is not active', () => {
|
it('should return false when the section is not active', () => {
|
||||||
@@ -309,7 +321,7 @@ describe('MenuService', () => {
|
|||||||
|
|
||||||
describe('isSectionVisible', () => {
|
describe('isSectionVisible', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getMenuSection').and.returnValue(observableOf(hiddenSection3));
|
spyOn(service, 'getMenuSection').and.returnValue(observableOf(hiddenSection3 as MenuSection));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when the section is hidden', () => {
|
it('should return false when the section is hidden', () => {
|
||||||
|
@@ -1,24 +1,35 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { MenuSection, MenuSectionIndex, MenuSections, MenusState, MenuState } from './menu.reducer';
|
import { MenuSection, MenuSections, MenuState } from './menu.reducer';
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
import { MenuID } from './initial-menus-state';
|
import { MenuID } from './initial-menus-state';
|
||||||
import { Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
ActivateMenuSectionAction,
|
ActivateMenuSectionAction,
|
||||||
AddMenuSectionAction,
|
AddMenuSectionAction,
|
||||||
CollapseMenuAction, CollapseMenuPreviewAction,
|
CollapseMenuAction,
|
||||||
|
CollapseMenuPreviewAction,
|
||||||
DeactivateMenuSectionAction,
|
DeactivateMenuSectionAction,
|
||||||
ExpandMenuAction, ExpandMenuPreviewAction,
|
ExpandMenuAction,
|
||||||
|
ExpandMenuPreviewAction,
|
||||||
HideMenuAction,
|
HideMenuAction,
|
||||||
RemoveMenuSectionAction,
|
RemoveMenuSectionAction,
|
||||||
ShowMenuAction,
|
ShowMenuAction,
|
||||||
ToggleActiveMenuSectionAction,
|
ToggleActiveMenuSectionAction,
|
||||||
ToggleMenuAction,
|
ToggleMenuAction,
|
||||||
} from './menu.actions';
|
} from './menu.actions';
|
||||||
import { hasNoValue, isNotEmpty } from '../empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../empty.util';
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
|
export function menuKeySelector<T>(key: string, selector): MemoizedSelector<MenuState, T> {
|
||||||
|
return createSelector(selector, (state) => {
|
||||||
|
if (hasValue(state)) {
|
||||||
|
return state[key];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const menusStateSelector = (state) => state.menus;
|
const menusStateSelector = (state) => state.menus;
|
||||||
|
|
||||||
@@ -28,20 +39,20 @@ const menuByIDSelector = (menuID: MenuID): MemoizedSelector<AppState, MenuState>
|
|||||||
|
|
||||||
const menuSectionStateSelector = (state: MenuState) => state.sections;
|
const menuSectionStateSelector = (state: MenuState) => state.sections;
|
||||||
|
|
||||||
const menuSectionByIDSelector = (id: string): MemoizedSelector<AppState, MenuSection> => {
|
const menuSectionByIDSelector = (id: string): MemoizedSelector<MenuState, MenuSection> => {
|
||||||
return keySelector<MenuSection>(id, menuSectionStateSelector);
|
return menuKeySelector<MenuSection>(id, menuSectionStateSelector);
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuSectionIndexStateSelector = (state: MenuState) => state.sectionToSubsectionIndex;
|
const menuSectionIndexStateSelector = (state: MenuState) => state.sectionToSubsectionIndex;
|
||||||
|
|
||||||
const getSubSectionsFromSectionSelector = (id: string): MemoizedSelector<AppState, MenuSectionIndex> => {
|
const getSubSectionsFromSectionSelector = (id: string): MemoizedSelector<MenuState, string[]> => {
|
||||||
return keySelector<MenuSectionIndex>(id, menuSectionIndexStateSelector);
|
return menuKeySelector<string[]>(id, menuSectionIndexStateSelector);
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class MenuService {
|
||||||
|
|
||||||
constructor(private store: Store<MenusState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -88,7 +88,7 @@ describe('ItemDetailPreviewComponent', () => {
|
|||||||
component.object = { hitHighlights: {} } as any;
|
component.object = { hitHighlights: {} } as any;
|
||||||
component.item = mockItem;
|
component.item = mockItem;
|
||||||
component.separator = ', ';
|
component.separator = ', ';
|
||||||
spyOn(component.item, 'getFiles').and.returnValue(mockItem.bundles);
|
spyOn(component.item, 'getFiles').and.returnValue(mockItem.bundles as any);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
@@ -13,7 +13,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|||||||
describe('ObjectDetailComponent', () => {
|
describe('ObjectDetailComponent', () => {
|
||||||
let comp: ObjectDetailComponent;
|
let comp: ObjectDetailComponent;
|
||||||
let fixture: ComponentFixture<ObjectDetailComponent>;
|
let fixture: ComponentFixture<ObjectDetailComponent>;
|
||||||
const testEvent = {test: 'test'};
|
const testEvent: any = {test: 'test'};
|
||||||
|
|
||||||
const testObjects = [
|
const testObjects = [
|
||||||
Object.assign (new DSpaceObject(), { one: 1 }),
|
Object.assign (new DSpaceObject(), { one: 1 }),
|
||||||
@@ -27,7 +27,7 @@ describe('ObjectDetailComponent', () => {
|
|||||||
Object.assign (new DSpaceObject(), { nine: 9 }),
|
Object.assign (new DSpaceObject(), { nine: 9 }),
|
||||||
Object.assign (new DSpaceObject(), { ten: 10 }),
|
Object.assign (new DSpaceObject(), { ten: 10 }),
|
||||||
];
|
];
|
||||||
const pageInfo = Object.assign(new PageInfo(), {elementsPerPage: 1, totalElements: 10, totalPages: 10, currentPage: 1})
|
const pageInfo = Object.assign(new PageInfo(), {elementsPerPage: 1, totalElements: 10, totalPages: 10, currentPage: 1});
|
||||||
const mockRD = createSuccessfulRemoteDataObject(new PaginatedList(pageInfo, testObjects));
|
const mockRD = createSuccessfulRemoteDataObject(new PaginatedList(pageInfo, testObjects));
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -7,7 +7,7 @@ import { SelectableListService } from './selectable-list/selectable-list.service
|
|||||||
describe('ObjectListComponent', () => {
|
describe('ObjectListComponent', () => {
|
||||||
let comp: ObjectListComponent;
|
let comp: ObjectListComponent;
|
||||||
let fixture: ComponentFixture<ObjectListComponent>;
|
let fixture: ComponentFixture<ObjectListComponent>;
|
||||||
const testEvent = { test: 'test' };
|
const testEvent: any = { test: 'test' };
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { async, TestBed } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { SelectableListService } from './selectable-list.service';
|
import { SelectableListService } from './selectable-list.service';
|
||||||
import { SelectableListsState } from './selectable-list.reducer';
|
|
||||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
import { hasValue } from '../../empty.util';
|
import { hasValue } from '../../empty.util';
|
||||||
import { SelectableListDeselectAction, SelectableListDeselectSingleAction, SelectableListSelectAction, SelectableListSelectSingleAction } from './selectable-list.actions';
|
import {
|
||||||
|
SelectableListDeselectAction,
|
||||||
|
SelectableListDeselectSingleAction,
|
||||||
|
SelectableListSelectAction,
|
||||||
|
SelectableListSelectSingleAction
|
||||||
|
} from './selectable-list.actions';
|
||||||
|
import { AppState } from '../../../app.reducer';
|
||||||
|
|
||||||
class SelectableObject extends ListableObject {
|
class SelectableObject extends ListableObject {
|
||||||
constructor(private value: string) {
|
constructor(private value: string) {
|
||||||
@@ -33,7 +38,7 @@ describe('SelectableListService', () => {
|
|||||||
const selected4 = new SelectableObject(value4);
|
const selected4 = new SelectableObject(value4);
|
||||||
|
|
||||||
let service: SelectableListService;
|
let service: SelectableListService;
|
||||||
const store: Store<SelectableListsState> = jasmine.createSpyObj('store', {
|
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
|
@@ -1,18 +1,20 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, startWith, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { SelectableListsState, SelectableListState } from './selectable-list.reducer';
|
import { SelectableListState } from './selectable-list.reducer';
|
||||||
import { AppState, keySelector } from '../../../app.reducer';
|
import { AppState, keySelector } from '../../../app.reducer';
|
||||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
import {
|
import {
|
||||||
SelectableListDeselectAction, SelectableListDeselectAllAction,
|
SelectableListDeselectAction,
|
||||||
SelectableListDeselectSingleAction, SelectableListSelectAction,
|
SelectableListDeselectAllAction,
|
||||||
|
SelectableListDeselectSingleAction,
|
||||||
|
SelectableListSelectAction,
|
||||||
SelectableListSelectSingleAction
|
SelectableListSelectSingleAction
|
||||||
} from './selectable-list.actions';
|
} from './selectable-list.actions';
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from '../../empty.util';
|
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||||
|
|
||||||
const selectableListsStateSelector = (state) => state.selectableLists;
|
const selectableListsStateSelector = (state: AppState) => state.selectableLists;
|
||||||
|
|
||||||
const menuByIDSelector = (id: string): MemoizedSelector<AppState, SelectableListState> => {
|
const menuByIDSelector = (id: string): MemoizedSelector<AppState, SelectableListState> => {
|
||||||
return keySelector<SelectableListState>(id, selectableListsStateSelector);
|
return keySelector<SelectableListState>(id, selectableListsStateSelector);
|
||||||
@@ -21,7 +23,7 @@ const menuByIDSelector = (id: string): MemoizedSelector<AppState, SelectableList
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SelectableListService {
|
export class SelectableListService {
|
||||||
|
|
||||||
constructor(private store: Store<SelectableListsState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -225,10 +225,14 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param cdRef
|
||||||
|
* ChangeDetectorRef is a singleton service provided by Angular.
|
||||||
* @param route
|
* @param route
|
||||||
* Route is a singleton service provided by Angular.
|
* Route is a singleton service provided by Angular.
|
||||||
* @param router
|
* @param router
|
||||||
* Router is a singleton service provided by Angular.
|
* Router is a singleton service provided by Angular.
|
||||||
|
* @param hostWindowService
|
||||||
|
* the HostWindowService singleton.
|
||||||
*/
|
*/
|
||||||
constructor(private cdRef: ChangeDetectorRef,
|
constructor(private cdRef: ChangeDetectorRef,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -243,7 +247,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
* The page being navigated to.
|
* The page being navigated to.
|
||||||
*/
|
*/
|
||||||
public doPageChange(page: number) {
|
public doPageChange(page: number) {
|
||||||
this.updateRoute({ page: page.toString() });
|
this.updateRoute({ pageId: this.id, page: page.toString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,7 +257,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
* The page size being navigated to.
|
* The page size being navigated to.
|
||||||
*/
|
*/
|
||||||
public doPageSizeChange(pageSize: number) {
|
public doPageSizeChange(pageSize: number) {
|
||||||
this.updateRoute({ page: 1, pageSize: pageSize });
|
this.updateRoute({ pageId: this.id, page: 1, pageSize: pageSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,7 +267,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
* The sort direction being navigated to.
|
* The sort direction being navigated to.
|
||||||
*/
|
*/
|
||||||
public doSortDirectionChange(sortDirection: SortDirection) {
|
public doSortDirectionChange(sortDirection: SortDirection) {
|
||||||
this.updateRoute({ page: 1, sortDirection: sortDirection });
|
this.updateRoute({ pageId: this.id, page: 1, sortDirection: sortDirection });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,7 +277,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
* The sort field being navigated to.
|
* The sort field being navigated to.
|
||||||
*/
|
*/
|
||||||
public doSortFieldChange(field: string) {
|
public doSortFieldChange(field: string) {
|
||||||
this.updateRoute({ page: 1, sortField: field });
|
this.updateRoute({ pageId: this.id, page: 1, sortField: field });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -413,6 +417,8 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
* Method to update all pagination variables to the current query parameters
|
* Method to update all pagination variables to the current query parameters
|
||||||
*/
|
*/
|
||||||
private setFields() {
|
private setFields() {
|
||||||
|
// set fields only when page id is the one configured for this pagination instance
|
||||||
|
if (this.currentQueryParams.pageId === this.id) {
|
||||||
// (+) converts string to a number
|
// (+) converts string to a number
|
||||||
const page = this.currentQueryParams.page;
|
const page = this.currentQueryParams.page;
|
||||||
if (this.currentPage !== +page) {
|
if (this.currentPage !== +page) {
|
||||||
@@ -435,6 +441,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to validate the current page value
|
* Method to validate the current page value
|
||||||
|
@@ -176,6 +176,7 @@ import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.componen
|
|||||||
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
||||||
import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
||||||
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
||||||
|
import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -395,7 +396,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
SearchFacetRangeOptionComponent,
|
SearchFacetRangeOptionComponent,
|
||||||
SearchAuthorityFilterComponent,
|
SearchAuthorityFilterComponent,
|
||||||
DsDynamicLookupRelationSearchTabComponent,
|
DsDynamicLookupRelationSearchTabComponent,
|
||||||
DsDynamicLookupRelationSelectionTabComponent
|
DsDynamicLookupRelationSelectionTabComponent,
|
||||||
|
DsDynamicLookupRelationExternalSourceTabComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
|
109
src/app/shared/sidebar/filter/sidebar-filter.service.spec.ts
Normal file
109
src/app/shared/sidebar/filter/sidebar-filter.service.spec.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
|
import * as ngrx from '@ngrx/store';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { sidebarFilterReducer } from './sidebar-filter.reducer';
|
||||||
|
import { SidebarFilterService } from './sidebar-filter.service';
|
||||||
|
import {
|
||||||
|
FilterCollapseAction,
|
||||||
|
FilterExpandAction,
|
||||||
|
FilterInitializeAction,
|
||||||
|
FilterToggleAction
|
||||||
|
} from './sidebar-filter.actions';
|
||||||
|
|
||||||
|
describe('SidebarFilterService', () => {
|
||||||
|
let service: SidebarFilterService;
|
||||||
|
let selectSpy;
|
||||||
|
let store: any;
|
||||||
|
let initialState;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
|
||||||
|
initialState = {
|
||||||
|
sidebarFilter: {
|
||||||
|
filter_1 : {
|
||||||
|
filterCollapsed: true
|
||||||
|
},
|
||||||
|
filter_2 : {
|
||||||
|
filterCollapsed: false
|
||||||
|
},
|
||||||
|
filter_3 : {
|
||||||
|
filterCollapsed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StoreModule.forRoot({ sidebarFilter: sidebarFilterReducer })
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideMockStore({ initialState }),
|
||||||
|
{ provide: SidebarFilterService, useValue: service }
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = TestBed.get(Store);
|
||||||
|
service = new SidebarFilterService(store);
|
||||||
|
selectSpy = spyOnProperty(ngrx, 'select').and.callThrough();
|
||||||
|
spyOn(store, 'dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initializeFilter', () => {
|
||||||
|
it('should dispatch an FilterInitializeAction with the correct arguments', () => {
|
||||||
|
service.initializeFilter('fakeFilter', true);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new FilterInitializeAction('fakeFilter', true));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('collapse', () => {
|
||||||
|
it('should dispatch an FilterInitializeAction with the correct arguments', () => {
|
||||||
|
service.collapse('fakeFilter');
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new FilterCollapseAction('fakeFilter'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('expand', () => {
|
||||||
|
it('should dispatch an FilterInitializeAction with the correct arguments', () => {
|
||||||
|
service.expand('fakeFilter');
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new FilterExpandAction('fakeFilter'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggle', () => {
|
||||||
|
it('should dispatch an FilterInitializeAction with the correct arguments', () => {
|
||||||
|
service.toggle('fakeFilter');
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new FilterToggleAction('fakeFilter'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isCollapsed', () => {
|
||||||
|
it('should return true', () => {
|
||||||
|
|
||||||
|
const result = service.isCollapsed('filter_1');
|
||||||
|
const expected = cold('b', {
|
||||||
|
b: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', () => {
|
||||||
|
|
||||||
|
const result = service.isCollapsed('filter_2');
|
||||||
|
const expected = cold('b', {
|
||||||
|
b: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -16,7 +16,7 @@ import { hasValue } from '../../empty.util';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SidebarFilterService {
|
export class SidebarFilterService {
|
||||||
|
|
||||||
constructor(private store:Store<SidebarFilterState>) {
|
constructor(private store:Store<SidebarFiltersState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,8 +3,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { StartsWithDateComponent } from './starts-with-date.component';
|
import { StartsWithDateComponent } from './starts-with-date.component';
|
||||||
@@ -159,7 +159,7 @@ describe('StartsWithDateComponent', () => {
|
|||||||
describe('when filling in the input form', () => {
|
describe('when filling in the input form', () => {
|
||||||
let form;
|
let form;
|
||||||
const expectedValue = '2015';
|
const expectedValue = '2015';
|
||||||
const extras = {
|
const extras: NavigationExtras = {
|
||||||
queryParams: Object.assign({ startsWith: expectedValue }),
|
queryParams: Object.assign({ startsWith: expectedValue }),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -66,7 +66,7 @@ describe('StartsWithTextComponent', () => {
|
|||||||
let select;
|
let select;
|
||||||
let input;
|
let input;
|
||||||
const expectedValue = '0';
|
const expectedValue = '0';
|
||||||
const extras = {
|
const extras: NavigationExtras = {
|
||||||
queryParams: Object.assign({ startsWith: expectedValue }),
|
queryParams: Object.assign({ startsWith: expectedValue }),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
@@ -96,7 +96,7 @@ describe('StartsWithTextComponent', () => {
|
|||||||
let select;
|
let select;
|
||||||
let input;
|
let input;
|
||||||
const expectedValue = options[1];
|
const expectedValue = options[1];
|
||||||
const extras = {
|
const extras: NavigationExtras = {
|
||||||
queryParams: Object.assign({ startsWith: expectedValue }),
|
queryParams: Object.assign({ startsWith: expectedValue }),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
@@ -126,7 +126,7 @@ describe('StartsWithTextComponent', () => {
|
|||||||
let optionLink;
|
let optionLink;
|
||||||
let input;
|
let input;
|
||||||
const expectedValue = options[1];
|
const expectedValue = options[1];
|
||||||
const extras = {
|
const extras: NavigationExtras = {
|
||||||
queryParams: Object.assign({ startsWith: expectedValue }),
|
queryParams: Object.assign({ startsWith: expectedValue }),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
@@ -154,7 +154,7 @@ describe('StartsWithTextComponent', () => {
|
|||||||
describe('when filling in the input form', () => {
|
describe('when filling in the input form', () => {
|
||||||
let form;
|
let form;
|
||||||
const expectedValue = 'A';
|
const expectedValue = 'A';
|
||||||
const extras = {
|
const extras: NavigationExtras = {
|
||||||
queryParams: Object.assign({ startsWith: expectedValue }),
|
queryParams: Object.assign({ startsWith: expectedValue }),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
|
@@ -20,7 +20,7 @@ describe('Angulartics2DSpace', () => {
|
|||||||
|
|
||||||
it('should use the statisticsService', () => {
|
it('should use the statisticsService', () => {
|
||||||
provider.startTracking();
|
provider.startTracking();
|
||||||
expect(statisticsService.trackViewEvent).toHaveBeenCalledWith('mock-object');
|
expect(statisticsService.trackViewEvent).toHaveBeenCalledWith('mock-object' as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -342,7 +342,7 @@ describe('SubmissionFormCollectionComponent Component', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(comp.searchField.reset).toHaveBeenCalled();
|
expect(comp.searchField.reset).toHaveBeenCalled();
|
||||||
expect(comp.collectionChange.emit).toHaveBeenCalledWith(submissionRestResponse[0]);
|
expect(comp.collectionChange.emit).toHaveBeenCalledWith(submissionRestResponse[0] as any);
|
||||||
expect(submissionServiceStub.changeSubmissionCollection).toHaveBeenCalled();
|
expect(submissionServiceStub.changeSubmissionCollection).toHaveBeenCalled();
|
||||||
expect(comp.selectedCollectionId).toBe(mockCollectionList[1].collection.id);
|
expect(comp.selectedCollectionId).toBe(mockCollectionList[1].collection.id);
|
||||||
expect(comp.selectedCollectionName$).toBeObservable(cold('(a|)', {
|
expect(comp.selectedCollectionName$).toBeObservable(cold('(a|)', {
|
||||||
|
@@ -37,6 +37,7 @@ import { getMockSectionUploadService } from '../../../../shared/mocks/mock-secti
|
|||||||
import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component';
|
||||||
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
|
|
||||||
function getMockFileService(): FileService {
|
function getMockFileService(): FileService {
|
||||||
return jasmine.createSpyObj('FileService', {
|
return jasmine.createSpyObj('FileService', {
|
||||||
@@ -104,7 +105,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => {
|
|||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
NgbModal,
|
NgbModal,
|
||||||
SubmissionSectionUploadFileComponent,
|
SubmissionSectionUploadFileComponent,
|
||||||
SubmissionSectionUploadFileEditComponent
|
SubmissionSectionUploadFileEditComponent,
|
||||||
|
FormBuilderService
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents().then();
|
}).compileComponents().then();
|
||||||
|
@@ -3,14 +3,7 @@ import { HttpHeaders } from '@angular/common/http';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable, of as observableOf, Subscription, timer as observableTimer } from 'rxjs';
|
import { Observable, of as observableOf, Subscription, timer as observableTimer } from 'rxjs';
|
||||||
import {
|
import { catchError, concatMap, distinctUntilChanged, filter, find, map, startWith, take, tap } from 'rxjs/operators';
|
||||||
catchError, concatMap,
|
|
||||||
distinctUntilChanged,
|
|
||||||
filter,
|
|
||||||
find,
|
|
||||||
map,
|
|
||||||
startWith, take, tap
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -28,7 +21,12 @@ import {
|
|||||||
SaveSubmissionSectionFormAction,
|
SaveSubmissionSectionFormAction,
|
||||||
SetActiveSectionAction
|
SetActiveSectionAction
|
||||||
} from './objects/submission-objects.actions';
|
} from './objects/submission-objects.actions';
|
||||||
import { SubmissionObjectEntry, SubmissionSectionEntry, SubmissionSectionError, SubmissionSectionObject } from './objects/submission-objects.reducer';
|
import {
|
||||||
|
SubmissionObjectEntry,
|
||||||
|
SubmissionSectionEntry,
|
||||||
|
SubmissionSectionError,
|
||||||
|
SubmissionSectionObject
|
||||||
|
} from './objects/submission-objects.reducer';
|
||||||
import { submissionObjectFromIdSelector } from './selectors';
|
import { submissionObjectFromIdSelector } from './selectors';
|
||||||
import { GlobalConfig } from '../../config/global-config.interface';
|
import { GlobalConfig } from '../../config/global-config.interface';
|
||||||
import { GLOBAL_CONFIG } from '../../config';
|
import { GLOBAL_CONFIG } from '../../config';
|
||||||
@@ -45,11 +43,7 @@ import { WorkspaceitemSectionsObject } from '../core/submission/models/workspace
|
|||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { ErrorResponse } from '../core/cache/response.models';
|
import { ErrorResponse } from '../core/cache/response.models';
|
||||||
import { RemoteDataError } from '../core/data/remote-data-error';
|
import { RemoteDataError } from '../core/data/remote-data-error';
|
||||||
import {
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject } from '../shared/testing/utils';
|
||||||
createFailedRemoteDataObject$,
|
|
||||||
createSuccessfulRemoteDataObject,
|
|
||||||
createSuccessfulRemoteDataObject$
|
|
||||||
} from '../shared/testing/utils';
|
|
||||||
import { RequestService } from '../core/data/request.service';
|
import { RequestService } from '../core/data/request.service';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
|
|
||||||
@@ -116,8 +110,8 @@ export class SubmissionService {
|
|||||||
*/
|
*/
|
||||||
createSubmission(collectionId?: string): Observable<SubmissionObject> {
|
createSubmission(collectionId?: string): Observable<SubmissionObject> {
|
||||||
return this.restService.postToEndpoint(this.workspaceLinkPath, {}, null, null, collectionId).pipe(
|
return this.restService.postToEndpoint(this.workspaceLinkPath, {}, null, null, collectionId).pipe(
|
||||||
map((workspaceitem: SubmissionObject) => workspaceitem[0]),
|
map((workspaceitem: SubmissionObject[]) => workspaceitem[0] as SubmissionObject),
|
||||||
catchError(() => observableOf({})))
|
catchError(() => observableOf({} as SubmissionObject)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -19,9 +19,7 @@ import { ClientCookieService } from '../../app/core/services/client-cookie.servi
|
|||||||
import { CookieService } from '../../app/core/services/cookie.service';
|
import { CookieService } from '../../app/core/services/cookie.service';
|
||||||
import { AuthService } from '../../app/core/auth/auth.service';
|
import { AuthService } from '../../app/core/auth/auth.service';
|
||||||
import { Angulartics2Module } from 'angulartics2';
|
import { Angulartics2Module } from 'angulartics2';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
|
||||||
import { SubmissionService } from '../../app/submission/submission.service';
|
import { SubmissionService } from '../../app/submission/submission.service';
|
||||||
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
|
|
||||||
import { StatisticsModule } from '../../app/statistics/statistics.module';
|
import { StatisticsModule } from '../../app/statistics/statistics.module';
|
||||||
|
|
||||||
export const REQ_KEY = makeStateKey<string>('req');
|
export const REQ_KEY = makeStateKey<string>('req');
|
||||||
@@ -50,7 +48,7 @@ export function getRequest(transferState: TransferState): any {
|
|||||||
IdlePreload
|
IdlePreload
|
||||||
}),
|
}),
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]),
|
Angulartics2Module.forRoot(),
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
DSpaceBrowserTransferStateModule,
|
DSpaceBrowserTransferStateModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
|
@@ -47,7 +47,7 @@ export function createTranslateLoader() {
|
|||||||
deps: []
|
deps: []
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]),
|
Angulartics2Module.forRoot(),
|
||||||
ServerModule,
|
ServerModule,
|
||||||
AppModule
|
AppModule
|
||||||
],
|
],
|
||||||
|
11
tslint.json
11
tslint.json
@@ -40,7 +40,7 @@
|
|||||||
],
|
],
|
||||||
"no-access-missing-member": false,
|
"no-access-missing-member": false,
|
||||||
"no-arg": true,
|
"no-arg": true,
|
||||||
"no-attribute-parameter-decorator": true,
|
"no-attribute-decorator": true,
|
||||||
"no-bitwise": true,
|
"no-bitwise": true,
|
||||||
"no-console": [
|
"no-console": [
|
||||||
true,
|
true,
|
||||||
@@ -81,9 +81,8 @@
|
|||||||
false
|
false
|
||||||
],
|
],
|
||||||
"prefer-const": true,
|
"prefer-const": true,
|
||||||
"pipe-naming": [
|
"pipe-prefix": [
|
||||||
true,
|
true,
|
||||||
"camelCase",
|
|
||||||
"ds"
|
"ds"
|
||||||
],
|
],
|
||||||
"quotemark": [
|
"quotemark": [
|
||||||
@@ -147,10 +146,10 @@
|
|||||||
"no-input-rename": true,
|
"no-input-rename": true,
|
||||||
"no-output-rename": true,
|
"no-output-rename": true,
|
||||||
"templates-use-public": false,
|
"templates-use-public": false,
|
||||||
"use-host-property-decorator": true,
|
"no-host-metadata-property": true,
|
||||||
"use-input-property-decorator": true,
|
"no-inputs-metadata-property": true,
|
||||||
"use-life-cycle-interface": false,
|
"use-life-cycle-interface": false,
|
||||||
"use-output-property-decorator": true,
|
"no-outputs-metadata-property": true,
|
||||||
"use-pipe-transform-interface": true
|
"use-pipe-transform-interface": true
|
||||||
// "rxjs-collapse-imports": true,
|
// "rxjs-collapse-imports": true,
|
||||||
// "rxjs-pipeable-operators-only": true,
|
// "rxjs-pipeable-operators-only": true,
|
||||||
|
@@ -126,12 +126,6 @@ module.exports = (env) => {
|
|||||||
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
loader: 'resolve-url-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
loader: 'sass-resources-loader',
|
loader: 'sass-resources-loader',
|
||||||
options: {
|
options: {
|
||||||
@@ -163,13 +157,7 @@ module.exports = (env) => {
|
|||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'resolve-url-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: true
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
const CompressionPlugin = require("compression-webpack-plugin");
|
const CompressionPlugin = require("compression-webpack-plugin");
|
||||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||||
const cssnano = require("cssnano");
|
const cssnano = require("cssnano");
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ module.exports = {
|
|||||||
|
|
||||||
|
|
||||||
new CompressionPlugin({
|
new CompressionPlugin({
|
||||||
asset: "[path].gz[query]",
|
filename: "[path].gz[query]",
|
||||||
algorithm: "gzip",
|
algorithm: "gzip",
|
||||||
test: /\.js$|\.css$|\.html$/,
|
test: /\.js$|\.css$|\.html$/,
|
||||||
threshold: 10240,
|
threshold: 10240,
|
||||||
@@ -39,15 +39,14 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new UglifyJsPlugin({
|
new TerserPlugin({
|
||||||
uglifyOptions: {
|
terserOptions: {
|
||||||
beautify: false,
|
beautify: false,
|
||||||
mangle: false,
|
mangle: false,
|
||||||
output: {
|
output: {
|
||||||
comments: false
|
comments: false
|
||||||
},
|
},
|
||||||
compress: {
|
compress: {
|
||||||
warnings: false,
|
|
||||||
conditionals: false,
|
conditionals: false,
|
||||||
unused: true,
|
unused: true,
|
||||||
comparisons: true,
|
comparisons: true,
|
||||||
@@ -58,7 +57,8 @@ module.exports = {
|
|||||||
join_vars: true,
|
join_vars: true,
|
||||||
negate_iife: true
|
negate_iife: true
|
||||||
},
|
},
|
||||||
sourceMap: true
|
sourceMap: true,
|
||||||
|
warnings: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new OptimizeCSSAssetsPlugin({
|
new OptimizeCSSAssetsPlugin({
|
||||||
|
@@ -167,12 +167,6 @@ module.exports = function (env) {
|
|||||||
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
loader: 'resolve-url-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
loader: 'sass-resources-loader',
|
loader: 'sass-resources-loader',
|
||||||
options: {
|
options: {
|
||||||
@@ -204,13 +198,7 @@ module.exports = function (env) {
|
|||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'resolve-url-loader',
|
|
||||||
options: {
|
|
||||||
sourceMap: true
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user