mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-09 11:03:05 +00:00
Merge branch 'response-cache-refactoring' into w2p-54472_Create-community-and-collection-pages
Conflicts: src/app/core/auth/auth-response-parsing.service.ts src/app/core/auth/auth.service.ts src/app/core/auth/models/auth-status.model.ts src/app/core/auth/models/normalized-auth-status.model.ts src/app/core/auth/server-auth.service.ts src/app/core/cache/builders/remote-data-build.service.ts src/app/core/data/collection-data.service.ts src/app/core/data/comcol-data.service.spec.ts src/app/core/data/comcol-data.service.ts src/app/core/data/community-data.service.ts src/app/core/data/data.service.spec.ts src/app/core/data/data.service.ts src/app/core/data/dspace-object-data.service.ts src/app/core/data/item-data.service.ts src/app/core/data/request.models.ts src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts src/app/core/shared/hal-endpoint.service.ts src/app/core/shared/operators.ts src/app/shared/testing/auth-request-service-stub.ts src/app/shared/testing/auth-service-stub.ts
This commit is contained in:
13
angular.json
Normal file
13
angular.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"cli": {
|
||||||
|
"defaultCollection": "@ngrx/schematics"
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"core": {
|
||||||
|
"root": "",
|
||||||
|
"projectType": "application"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,9 +18,17 @@ module.exports = {
|
|||||||
// Caching settings
|
// Caching settings
|
||||||
cache: {
|
cache: {
|
||||||
// NOTE: how long should objects be cached for by default
|
// NOTE: how long should objects be cached for by default
|
||||||
msToLive: 15 * 60 * 1000, // 15 minutes
|
msToLive: {
|
||||||
|
default: 15 * 60 * 1000, // 15 minutes
|
||||||
|
exportToZip: 5 * 1000 // 5 seconds
|
||||||
|
},
|
||||||
// msToLive: 1000, // 15 minutes
|
// msToLive: 1000, // 15 minutes
|
||||||
control: 'max-age=60' // revalidate browser
|
control: 'max-age=60', // revalidate browser
|
||||||
|
autoSync: {
|
||||||
|
defaultTime: 0,
|
||||||
|
maxBufferSize: 100,
|
||||||
|
timePerMethod: {'PATCH': 3} //time in seconds
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// Form settings
|
// Form settings
|
||||||
form: {
|
form: {
|
||||||
|
175
package.json
175
package.json
@@ -23,9 +23,9 @@
|
|||||||
"prebuild": "yarn run clean:dist",
|
"prebuild": "yarn run clean:dist",
|
||||||
"prebuild:aot": "yarn run prebuild",
|
"prebuild:aot": "yarn run prebuild",
|
||||||
"prebuild:prod": "yarn run prebuild",
|
"prebuild:prod": "yarn run prebuild",
|
||||||
"build": "webpack --progress",
|
"build": "webpack --progress --mode development",
|
||||||
"build:aot": "webpack --env.aot --env.server && webpack --env.aot --env.client",
|
"build:aot": "webpack --env.aot --env.server --mode development && webpack --env.aot --env.client --mode development",
|
||||||
"build:prod": "webpack --env.aot --env.server -p && webpack --env.aot --env.client -p",
|
"build:prod": "webpack --env.aot --env.server --env.production && webpack --env.aot --env.client --env.production",
|
||||||
"postbuild:prod": "yarn run rollup",
|
"postbuild:prod": "yarn run rollup",
|
||||||
"rollup": "rollup -c rollup.config.js",
|
"rollup": "rollup -c rollup.config.js",
|
||||||
"prestart": "yarn run build:prod",
|
"prestart": "yarn run build:prod",
|
||||||
@@ -40,21 +40,15 @@
|
|||||||
"server": "node dist/server.js",
|
"server": "node dist/server.js",
|
||||||
"server:watch": "nodemon dist/server.js",
|
"server:watch": "nodemon dist/server.js",
|
||||||
"server:watch:debug": "nodemon --debug dist/server.js",
|
"server:watch:debug": "nodemon --debug dist/server.js",
|
||||||
"webpack:watch": "webpack -w",
|
"webpack:watch": "webpack -w --mode development",
|
||||||
"webpack:watch:aot": "webpack -w --env.aot --env.server && webpack -w --env.aot --env.client",
|
|
||||||
"webpack:watch:prod": "webpack -w --env.aot --env.server -p && webpack -w --env.aot --env.client -p",
|
|
||||||
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
|
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
|
||||||
"watch:aot": "yarn run build:aot && npm-run-all -p webpack:watch:aot server:watch",
|
|
||||||
"watch:prod": "yarn run build:prod && npm-run-all -p webpack:watch:prod server:watch",
|
|
||||||
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
|
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
|
||||||
"watch:debug:aot": "yarn run build:aot && npm-run-all -p webpack:watch:aot server:watch:debug",
|
|
||||||
"watch:debug:prod": "yarn run build:prod && npm-run-all -p webpack:watch:prod server:watch:debug",
|
|
||||||
"predebug": "yarn run build",
|
"predebug": "yarn run build",
|
||||||
"predebug:server": "yarn run build",
|
"predebug:server": "yarn run build",
|
||||||
"debug": "node --debug-brk dist/server.js",
|
"debug": "node --debug-brk dist/server.js",
|
||||||
"debug:server": "node-nightly --inspect --debug-brk dist/server.js",
|
"debug:server": "node-nightly --inspect --debug-brk dist/server.js",
|
||||||
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js",
|
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --mode development",
|
||||||
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server -p",
|
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production",
|
||||||
"ci": "yarn run lint && yarn run build:aot && yarn run test:headless && npm-run-all -p -r server e2e",
|
"ci": "yarn run lint && yarn run build:aot && yarn run test:headless && npm-run-all -p -r server e2e",
|
||||||
"protractor": "node node_modules/protractor/bin/protractor",
|
"protractor": "node node_modules/protractor/bin/protractor",
|
||||||
"pree2e": "yarn run webdriver:update",
|
"pree2e": "yarn run webdriver:update",
|
||||||
@@ -69,40 +63,42 @@
|
|||||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage"
|
"coverage": "http-server -c-1 -o -p 9875 ./coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^5.2.5",
|
"@angular/animations": "^6.1.4",
|
||||||
"@angular/common": "^5.2.5",
|
"@angular/cli": "^6.1.5",
|
||||||
"@angular/core": "^5.2.5",
|
"@angular/common": "^6.1.4",
|
||||||
"@angular/forms": "^5.2.5",
|
"@angular/core": "^6.1.4",
|
||||||
"@angular/http": "^5.2.5",
|
"@angular/forms": "^6.1.4",
|
||||||
"@angular/platform-browser": "^5.2.5",
|
"@angular/http": "^6.1.4",
|
||||||
"@angular/platform-browser-dynamic": "^5.2.5",
|
"@angular/platform-browser": "^6.1.4",
|
||||||
"@angular/platform-server": "^5.2.5",
|
"@angular/platform-browser-dynamic": "^6.1.4",
|
||||||
"@angular/router": "^5.2.5",
|
"@angular/platform-server": "^6.1.4",
|
||||||
|
"@angular/router": "^6.1.4",
|
||||||
"@angularclass/bootloader": "1.0.1",
|
"@angularclass/bootloader": "1.0.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
||||||
"@ng-dynamic-forms/core": "5.4.7",
|
"@ng-dynamic-forms/core": "6.0.9",
|
||||||
"@ng-dynamic-forms/ui-ng-bootstrap": "5.4.7",
|
"@ng-dynamic-forms/ui-ng-bootstrap": "6.0.9",
|
||||||
"@ngrx/effects": "^5.1.0",
|
"@ngrx/effects": "^6.1.0",
|
||||||
"@ngrx/router-store": "^5.0.1",
|
"@ngrx/router-store": "^6.1.0",
|
||||||
"@ngrx/store": "^5.1.0",
|
"@ngrx/store": "^6.1.0",
|
||||||
"@nguniversal/express-engine": "5.0.0",
|
"@nguniversal/express-engine": "6.1.0",
|
||||||
"@ngx-translate/core": "9.1.1",
|
"@ngx-translate/core": "10.0.2",
|
||||||
"@ngx-translate/http-loader": "2.0.1",
|
"@ngx-translate/http-loader": "3.0.1",
|
||||||
"@nicky-lenaers/ngx-scroll-to": "^0.6.0",
|
"@nicky-lenaers/ngx-scroll-to": "^1.0.0",
|
||||||
"angular-idle-preload": "2.0.4",
|
"angular-idle-preload": "3.0.0",
|
||||||
"angular2-moment": "^1.9.0",
|
|
||||||
"angular-sortablejs": "^2.5.0",
|
"angular-sortablejs": "^2.5.0",
|
||||||
"angular2-text-mask": "8.0.4",
|
"angular2-text-mask": "9.0.0",
|
||||||
"angulartics2": "^5.2.0",
|
"angulartics2": "^6.2.0",
|
||||||
"body-parser": "1.18.2",
|
"body-parser": "1.18.2",
|
||||||
"bootstrap": "4.1.1",
|
"bootstrap": "4.1.3",
|
||||||
"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.3",
|
"core-js": "^2.5.7",
|
||||||
"express": "4.16.2",
|
"express": "4.16.2",
|
||||||
"express-session": "1.15.6",
|
"express-session": "1.15.6",
|
||||||
|
"fast-json-patch": "^2.0.7",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^0.4.10",
|
||||||
"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",
|
||||||
@@ -112,110 +108,117 @@
|
|||||||
"methods": "1.1.2",
|
"methods": "1.1.2",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
"morgan": "1.9.0",
|
"morgan": "1.9.0",
|
||||||
"ng2-nouislider": "^1.7.11",
|
"ng-mocks": "^6.2.1",
|
||||||
"ng2-file-upload": "1.2.1",
|
"ng2-file-upload": "1.2.1",
|
||||||
"ngx-infinite-scroll": "0.8.2",
|
"ng2-nouislider": "^1.7.11",
|
||||||
|
"ngx-bootstrap": "^3.0.1",
|
||||||
|
"ngx-infinite-scroll": "6.0.1",
|
||||||
|
"ngx-moment": "^3.1.0",
|
||||||
"ngx-pagination": "3.0.3",
|
"ngx-pagination": "3.0.3",
|
||||||
"nouislider": "^11.0.0",
|
"nouislider": "^11.0.0",
|
||||||
"pem": "1.12.3",
|
"pem": "1.12.3",
|
||||||
"reflect-metadata": "0.1.12",
|
"reflect-metadata": "0.1.12",
|
||||||
"rxjs": "5.5.6",
|
"rxjs": "6.2.2",
|
||||||
"sortablejs": "1.7.0",
|
"sortablejs": "1.7.0",
|
||||||
"text-mask-core": "5.0.1",
|
"text-mask-core": "5.0.1",
|
||||||
|
"ts-loader": "^5.2.1",
|
||||||
"ts-md5": "^1.2.4",
|
"ts-md5": "^1.2.4",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"zone.js": "0.8.20"
|
"webpack-cli": "^3.1.0",
|
||||||
|
"zone.js": "^0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler": "^5.2.5",
|
"@angular/compiler": "^6.1.4",
|
||||||
"@angular/compiler-cli": "^5.2.5",
|
"@angular/compiler-cli": "^6.1.4",
|
||||||
"@ngrx/store-devtools": "^5.1.0",
|
"@ngrx/entity": "^6.1.0",
|
||||||
"@ngtools/webpack": "^1.10.0",
|
"@ngrx/schematics": "^6.1.0",
|
||||||
|
"@ngrx/store-devtools": "^6.1.0",
|
||||||
|
"@ngtools/webpack": "^6.1.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",
|
||||||
"@types/deep-freeze": "0.1.1",
|
"@types/deep-freeze": "0.1.1",
|
||||||
"@types/express": "^4.11.1",
|
"@types/express": "^4.11.1",
|
||||||
"@types/express-serve-static-core": "4.11.1",
|
"@types/express-serve-static-core": "4.16.0",
|
||||||
"@types/hammerjs": "2.0.35",
|
"@types/hammerjs": "2.0.35",
|
||||||
"@types/jasmine": "^2.8.6",
|
"@types/jasmine": "^2.8.6",
|
||||||
"@types/js-cookie": "2.1.0",
|
"@types/js-cookie": "2.1.0",
|
||||||
|
"@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": "^9.4.6",
|
"@types/node": "^10.9.4",
|
||||||
"@types/serve-static": "1.13.1",
|
"@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",
|
||||||
"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": "^8.0.0",
|
"autoprefixer": "^9.1.3",
|
||||||
"awesome-typescript-loader": "3.4.1",
|
|
||||||
"caniuse-lite": "^1.0.30000697",
|
"caniuse-lite": "^1.0.30000697",
|
||||||
"codelyzer": "^4.1.0",
|
"codelyzer": "^4.4.4",
|
||||||
"compression-webpack-plugin": "^1.1.6",
|
"compression-webpack-plugin": "^1.1.6",
|
||||||
"copy-webpack-plugin": "^4.4.1",
|
"copy-webpack-plugin": "^4.4.1",
|
||||||
"coveralls": "3.0.0",
|
"coveralls": "3.0.0",
|
||||||
"css-loader": "0.28.9",
|
"css-loader": "1.0.0",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"html-webpack-plugin": "2.30.1",
|
"html-webpack-plugin": "^4.0.0-alpha",
|
||||||
"imports-loader": "0.7.1",
|
"imports-loader": "0.8.0",
|
||||||
"istanbul-instrumenter-loader": "3.0.0",
|
"istanbul-instrumenter-loader": "3.0.1",
|
||||||
"jasmine-core": "^2.99.1",
|
"jasmine-core": "^3.2.1",
|
||||||
"jasmine-marbles": "0.2.0",
|
"jasmine-marbles": "0.3.1",
|
||||||
"jasmine-spec-reporter": "4.2.1",
|
"jasmine-spec-reporter": "4.2.1",
|
||||||
"json-loader": "0.5.7",
|
"karma": "3.0.0",
|
||||||
"karma": "2.0.0",
|
|
||||||
"karma-chrome-launcher": "2.2.0",
|
"karma-chrome-launcher": "2.2.0",
|
||||||
"karma-cli": "1.0.1",
|
"karma-cli": "1.0.1",
|
||||||
"karma-coverage": "1.1.1",
|
"karma-coverage": "1.1.2",
|
||||||
"karma-istanbul-preprocessor": "0.0.2",
|
"karma-istanbul-preprocessor": "0.0.2",
|
||||||
"karma-jasmine": "1.1.1",
|
"karma-jasmine": "1.1.2",
|
||||||
"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",
|
||||||
"karma-remap-istanbul": "0.6.0",
|
"karma-remap-istanbul": "0.6.0",
|
||||||
"karma-sourcemap-loader": "0.3.7",
|
"karma-sourcemap-loader": "0.3.7",
|
||||||
"karma-webdriver-launcher": "1.0.5",
|
"karma-webdriver-launcher": "1.0.5",
|
||||||
"karma-webpack": "2.0.9",
|
"karma-webpack": "3.0.0",
|
||||||
"ngrx-store-freeze": "^0.2.1",
|
"ngrx-store-freeze": "^0.2.4",
|
||||||
"node-sass": "^4.7.2",
|
"node-sass": "^4.7.2",
|
||||||
"nodemon": "^1.15.0",
|
"nodemon": "^1.15.0",
|
||||||
"npm-run-all": "4.1.2",
|
"npm-run-all": "4.1.3",
|
||||||
"postcss": "^6.0.18",
|
"postcss": "^7.0.2",
|
||||||
"postcss-apply": "0.8.0",
|
"postcss-apply": "0.11.0",
|
||||||
"postcss-cli": "^5.0.0",
|
"postcss-cli": "^6.0.0",
|
||||||
"postcss-cssnext": "3.1.0",
|
"postcss-cssnext": "3.1.0",
|
||||||
"postcss-loader": "^2.1.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-responsive-type": "1.0.0",
|
"postcss-responsive-type": "1.0.0",
|
||||||
"postcss-smart-import": "0.7.6",
|
"postcss-smart-import": "0.7.6",
|
||||||
"protractor": "^5.3.0",
|
"protractor": "^5.3.0",
|
||||||
"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.2.1",
|
"resolve-url-loader": "^2.3.0",
|
||||||
"rimraf": "2.6.2",
|
"rimraf": "2.6.2",
|
||||||
"rollup": "^0.56.0",
|
"rollup": "^0.65.0",
|
||||||
"rollup-plugin-commonjs": "^8.3.0",
|
"rollup-plugin-commonjs": "^9.1.6",
|
||||||
"rollup-plugin-node-globals": "1.1.0",
|
"rollup-plugin-node-globals": "1.2.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.3",
|
"rollup-plugin-node-resolve": "^3.0.3",
|
||||||
"rollup-plugin-uglify": "3.0.0",
|
"rollup-plugin-terser": "^2.0.2",
|
||||||
"sass-loader": "6.0.6",
|
"sass-loader": "7.1.0",
|
||||||
"script-ext-html-webpack-plugin": "1.8.8",
|
"script-ext-html-webpack-plugin": "2.0.1",
|
||||||
"source-map": "0.6.1",
|
"source-map": "0.7.3",
|
||||||
"source-map-loader": "0.2.3",
|
"source-map-loader": "0.2.4",
|
||||||
"string-replace-loader": "1.3.0",
|
"string-replace-loader": "2.1.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.9.1",
|
"tslint": "5.11.0",
|
||||||
"typedoc": "^0.9.0",
|
"typedoc": "^0.9.0",
|
||||||
"typescript": "2.6.2",
|
"typescript": "^2.9.1",
|
||||||
"webpack": "^3.11.0",
|
"webpack": "^4.17.1",
|
||||||
"webpack-bundle-analyzer": "^2.10.0",
|
"webpack-bundle-analyzer": "^2.13.1",
|
||||||
"webpack-dev-middleware": "^2.0.5",
|
"webpack-dev-middleware": "3.2.0",
|
||||||
"webpack-dev-server": "2.11.1",
|
"webpack-dev-server": "^3.1.5",
|
||||||
"webpack-merge": "4.1.1",
|
"webpack-merge": "4.1.4",
|
||||||
"webpack-node-externals": "1.6.0"
|
"webpack-node-externals": "1.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
277
resources/i18n/cs.json
Normal file
277
resources/i18n/cs.json
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
{
|
||||||
|
"footer": {
|
||||||
|
"copyright": "copyright © 2002-{{ year }}",
|
||||||
|
"link.dspace": "software DSpace",
|
||||||
|
"link.duraspace": "DuraSpace"
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"page": {
|
||||||
|
"news": "Novinky",
|
||||||
|
"license": "Licence",
|
||||||
|
"browse": {
|
||||||
|
"recent": {
|
||||||
|
"head": "Poslední příspěvky"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"community": {
|
||||||
|
"page": {
|
||||||
|
"news": "Novinky",
|
||||||
|
"license": "Licence"
|
||||||
|
},
|
||||||
|
"sub-collection-list": {
|
||||||
|
"head": "Kolekce v této komunitě"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"page": {
|
||||||
|
"author": "Autor",
|
||||||
|
"abstract": "Abstract",
|
||||||
|
"date": "Datum",
|
||||||
|
"uri": "URI",
|
||||||
|
"files": "Soubory",
|
||||||
|
"collections": "Kolekce",
|
||||||
|
"filesection": {
|
||||||
|
"download": "Stáhnout",
|
||||||
|
"name": "Název:",
|
||||||
|
"format": "Formát:",
|
||||||
|
"size": "Velikost:",
|
||||||
|
"description": "Popis:"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"simple": "Minimální záznam",
|
||||||
|
"full": "Úplný záznam"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"home": "Domů",
|
||||||
|
"login": "Přihlásit se",
|
||||||
|
"logout": "Odhlásit se"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"results-per-page": "Výsledků na stránku",
|
||||||
|
"sort-direction": "Seřazení",
|
||||||
|
"showing": {
|
||||||
|
"label": "Zobrazují se záznamy ",
|
||||||
|
"detail": "{{ range }} z {{ total }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sorting": {
|
||||||
|
"score": {
|
||||||
|
"DESC": "Relevance"
|
||||||
|
},
|
||||||
|
"dc.title": {
|
||||||
|
"ASC": "Název vzestupně",
|
||||||
|
"DESC": "Název sestupně"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "DSpace",
|
||||||
|
"404": {
|
||||||
|
"help": "Nepodařilo se najít stránku, kterou hledáte. Je možné, že stránka byla přesunuta nebo smazána. Pomocí tlačítka níže můžete přejít na domovskou stránku. ",
|
||||||
|
"page-not-found": "stránka nenalezena",
|
||||||
|
"link": {
|
||||||
|
"home-page": "Přejít na domovskou stránku"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "DSpace Angular :: Domů",
|
||||||
|
"description": "",
|
||||||
|
"top-level-communities": {
|
||||||
|
"head": "Komunity v DSpace",
|
||||||
|
"help": "Vybráním komunity můžete prohlížet její kolekce."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "DSpace Angular :: Hledat",
|
||||||
|
"description": "",
|
||||||
|
"form": {
|
||||||
|
"search": "Hledat",
|
||||||
|
"search_dspace": "Hledat v DSpace"
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"head": "Výsledky hledání",
|
||||||
|
"no-results": "Nebyli nalezeny žádné výsledky"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"close": "Zpět na výsledky",
|
||||||
|
"open": "Vyhledávací nástroje",
|
||||||
|
"results": "výsledky",
|
||||||
|
"filters": {
|
||||||
|
"title": "Filtry"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Nastavení",
|
||||||
|
"sort-by": "Řadit dle",
|
||||||
|
"rpp": "Výsledků na stránku"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"view-switch": {
|
||||||
|
"show-list": "Zobrazit seznam",
|
||||||
|
"show-grid": "Zobrazit mřížku"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"head": "Filtry",
|
||||||
|
"reset": "Obnovit filtry",
|
||||||
|
"applied": {
|
||||||
|
"f.author": "Autor",
|
||||||
|
"f.dateIssued.min": "Od data",
|
||||||
|
"f.dateIssued.max": "Do data",
|
||||||
|
"f.subject": "Předmět",
|
||||||
|
"f.has_content_in_original_bundle": "Má soubory"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"show-more": "Zobrazit více",
|
||||||
|
"show-less": "Sbalit",
|
||||||
|
"author": {
|
||||||
|
"placeholder": "Jméno autora",
|
||||||
|
"head": "Autor"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"placeholder": "Filtr rozsahu",
|
||||||
|
"head": "Rozsah"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"placeholder": "Předmět",
|
||||||
|
"head": "Předmět"
|
||||||
|
},
|
||||||
|
"dateIssued": {
|
||||||
|
"max": {
|
||||||
|
"placeholder": "Datum od"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"placeholder": "Datum do"
|
||||||
|
},
|
||||||
|
"head": "Datum"
|
||||||
|
},
|
||||||
|
"has_content_in_original_bundle": {
|
||||||
|
"head": "Má soubory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browse": {
|
||||||
|
"title": "Prohlížíte {{ collection }} dle {{ field }} {{ value }}"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"registries": {
|
||||||
|
"metadata": {
|
||||||
|
"title": "DSpace Angular :: Registr metadat",
|
||||||
|
"head": "Registr metadat",
|
||||||
|
"description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu kvalifikový Dublin Core.",
|
||||||
|
"schemas": {
|
||||||
|
"table": {
|
||||||
|
"id": "ID",
|
||||||
|
"namespace": "Jmenný prostor",
|
||||||
|
"name": "Název"
|
||||||
|
},
|
||||||
|
"no-items": "Žádná schémata metadat."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"title": "DSpace Angular :: Registr schémat metadat",
|
||||||
|
"head": "Metadata Schema",
|
||||||
|
"description": "Toto je schéma metadat pro „{{namespace}}“.",
|
||||||
|
"fields": {
|
||||||
|
"head": "Pole schématu metadat",
|
||||||
|
"table": {
|
||||||
|
"field": "Pole",
|
||||||
|
"scopenote": "Poznámka o rozsahu"
|
||||||
|
},
|
||||||
|
"no-items": "Žádná metadatová pole."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bitstream-formats": {
|
||||||
|
"title": "DSpace Angular :: Registr formátů souborů",
|
||||||
|
"head": "Registr formátů souborů",
|
||||||
|
"description": "Tento seznam formátů souborů poskytuje informace o známých formátech a o úrovni jejich podpory.",
|
||||||
|
"formats": {
|
||||||
|
"table": {
|
||||||
|
"name": "Název",
|
||||||
|
"mimetype": "Typ MIME",
|
||||||
|
"supportLevel": {
|
||||||
|
"head": "Úroveň podpory",
|
||||||
|
"0": "Neznámá",
|
||||||
|
"1": "Známá",
|
||||||
|
"2": "Podpora"
|
||||||
|
},
|
||||||
|
"internal": "interní"
|
||||||
|
},
|
||||||
|
"no-items": "Žádné formáty souborů."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"default": "Načítá se...",
|
||||||
|
"top-level-communities": "Načítají se komunity nejvyšší úrovně...",
|
||||||
|
"community": "Načítá se komunita...",
|
||||||
|
"collection": "Načítá se kolekce...",
|
||||||
|
"sub-collections": "Načítají se subkolekce...",
|
||||||
|
"recent-submissions": "Načítají se poslední příspěvky...",
|
||||||
|
"item": "Načítá se záznam...",
|
||||||
|
"objects": "Načítá se...",
|
||||||
|
"search-results": "Načítají se výsledky hledání...",
|
||||||
|
"browse-by": "Načítají se záznamy..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"default": "Chyba",
|
||||||
|
"top-level-communities": "Chyba během stahování komunit nejvyšší úrovně",
|
||||||
|
"community": "Chyba během stahování komunity",
|
||||||
|
"collection": "Chyba během stahování kolekce",
|
||||||
|
"sub-collections": "Chyba během stahování subkolekcí",
|
||||||
|
"recent-submissions": "Chyba během stahování posledních příspěvků",
|
||||||
|
"item": "Chyba během stahování záznamu",
|
||||||
|
"objects": "Chyba během stahování objektů",
|
||||||
|
"search-results": "Chyba během stahování výsledků hledání",
|
||||||
|
"browse-by": "Chyba během stahování záznamů",
|
||||||
|
"validation": {
|
||||||
|
"pattern": "Tento vstup je omezen dle vzoru: {{ pattern }}.",
|
||||||
|
"license": {
|
||||||
|
"notgranted": "Pro dokončení zaslání Musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"submit": "Odeslat",
|
||||||
|
"cancel": "Zrušit",
|
||||||
|
"search": "Hledat",
|
||||||
|
"remove": "Smazat",
|
||||||
|
"first-name": "Křestní jméno",
|
||||||
|
"last-name": "Příjmení",
|
||||||
|
"loading": "Načítá se...",
|
||||||
|
"no-results": "Nebyli nalezeny žádné výsledky",
|
||||||
|
"no-value": "Nebyla zadána hodnota",
|
||||||
|
"group-collapse": "Sbalit",
|
||||||
|
"group-expand": "Rozbalit",
|
||||||
|
"group-collapse-help": "Kliknutím sem sbalíte",
|
||||||
|
"group-expand-help": "Kliknutím sem rozbalíte a přidáte další prvky"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Přihlásit se",
|
||||||
|
"form": {
|
||||||
|
"header": "Prosím, přihlaste se do DSpace",
|
||||||
|
"email": "E-mailová adresa",
|
||||||
|
"forgot-password": "Zapomněli jste své heslo?",
|
||||||
|
"new-user": "Nový uživatel? Zaregistrujte se kliknutím sem.",
|
||||||
|
"password": "Heslo",
|
||||||
|
"submit": "Přihlásit se"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"title": "Odhlásit se",
|
||||||
|
"form": {
|
||||||
|
"header": "Odhlásit se z DSpace",
|
||||||
|
"submit": "Odhlásit se"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"messages": {
|
||||||
|
"expired": "Vaše relace vypršela. Prosím, znova se přihlaste."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"invalid-user": "Neplatná e-mailová adresa nebo heslo."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
277
resources/i18n/de.json
Normal file
277
resources/i18n/de.json
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
{
|
||||||
|
"footer": {
|
||||||
|
"copyright": "Copyright © 2002-{{ year }}",
|
||||||
|
"link.dspace": "DSpace Software",
|
||||||
|
"link.duraspace": "DuraSpace"
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"page": {
|
||||||
|
"news": "Neuigkeiten",
|
||||||
|
"license": "Lizenz",
|
||||||
|
"browse": {
|
||||||
|
"recent": {
|
||||||
|
"head": "Aktuellste Veröffentlichungen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"community": {
|
||||||
|
"page": {
|
||||||
|
"news": "Neuigkeiten",
|
||||||
|
"license": "Lizenz"
|
||||||
|
},
|
||||||
|
"sub-collection-list": {
|
||||||
|
"head": "Sammlungen in diesem Bereich"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"page": {
|
||||||
|
"author": "Autor",
|
||||||
|
"abstract": "Kurzfassung",
|
||||||
|
"date": "Datum",
|
||||||
|
"uri": "URI",
|
||||||
|
"files": "Dateien",
|
||||||
|
"collections": "Sammlungen",
|
||||||
|
"filesection": {
|
||||||
|
"download": "Herunterladen",
|
||||||
|
"name": "Name:",
|
||||||
|
"format": "Format:",
|
||||||
|
"size": "Größe:",
|
||||||
|
"description": "Beschreibung:"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"simple": "Kurzanzeige",
|
||||||
|
"full": "Vollanzeige"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"home": "Zur Startseite",
|
||||||
|
"login": "Anmelden",
|
||||||
|
"logout": "Abmelden"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"results-per-page": "Ergebnisse pro Seite",
|
||||||
|
"sort-direction": "Sortiermöglichkeiten",
|
||||||
|
"showing": {
|
||||||
|
"label": "Anzeige der Treffer ",
|
||||||
|
"detail": "{{ range }} bis {{ total }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sorting": {
|
||||||
|
"score": {
|
||||||
|
"DESC": "Relevanz"
|
||||||
|
},
|
||||||
|
"dc.title": {
|
||||||
|
"ASC": "Titel aufsteigend",
|
||||||
|
"DESC": "Titel absteigend"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "DSpace",
|
||||||
|
"404": {
|
||||||
|
"help": "Die Seite, die Sie aufrufen wollten, konnte nicht gefunden werden. Sie könnte verschoben oder gelöscht worden sein. Mit dem Link unten kommen Sie zurück zur Startseite. ",
|
||||||
|
"page-not-found": "Seite nicht gefunden",
|
||||||
|
"link": {
|
||||||
|
"home-page": "Zurück zur Startseite"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "DSpace Angular :: Startseite",
|
||||||
|
"description": "",
|
||||||
|
"top-level-communities": {
|
||||||
|
"head": "Bereiche in DSpace",
|
||||||
|
"help": "Wählen Sie einen Bereich, um seine Sammlungen einzusehen."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "DSpace Angular :: Suche",
|
||||||
|
"description": "",
|
||||||
|
"form": {
|
||||||
|
"search": "Suche",
|
||||||
|
"search_dspace": "DSpace durchsuchen"
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"head": "Suchergebnisse",
|
||||||
|
"no-results": "Zu dieser Suche gibt es keine Treffer."
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"close": "Zurück zu den Ergebnissen",
|
||||||
|
"open": "Suchwerkzeuge",
|
||||||
|
"results": "Ergebnisse",
|
||||||
|
"filters": {
|
||||||
|
"title": "Filter"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Einstellungen",
|
||||||
|
"sort-by": "Sortiere nach",
|
||||||
|
"rpp": "Treffer pro Seite"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"view-switch": {
|
||||||
|
"show-list": "Zeige als Liste",
|
||||||
|
"show-grid": "Zeige als Raster"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"head": "Filter",
|
||||||
|
"reset": "Filter zurücksetzen",
|
||||||
|
"applied": {
|
||||||
|
"f.author": "Autor",
|
||||||
|
"f.dateIssued.min": "Anfangsdatum",
|
||||||
|
"f.dateIssued.max": "Enddatum",
|
||||||
|
"f.subject": "Thema",
|
||||||
|
"f.has_content_in_original_bundle": "Besitzt Dateien"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"show-more": "Zeige mehr",
|
||||||
|
"show-less": "Zeige weniger",
|
||||||
|
"author": {
|
||||||
|
"placeholder": "Autor",
|
||||||
|
"head": "Autor"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"placeholder": "Bereichsfilter",
|
||||||
|
"head": "Bereich"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"placeholder": "Schlagwort",
|
||||||
|
"head": "Schlagwort"
|
||||||
|
},
|
||||||
|
"dateIssued": {
|
||||||
|
"max": {
|
||||||
|
"placeholder": "Frühestes Datum"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"placeholder": "Ältestes Datum"
|
||||||
|
},
|
||||||
|
"head": "Datum"
|
||||||
|
},
|
||||||
|
"has_content_in_original_bundle": {
|
||||||
|
"head": "Besitzt Dateien"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browse": {
|
||||||
|
"title": "Anzeige {{ collection }} nach {{ field }} {{ value }}"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"registries": {
|
||||||
|
"metadata": {
|
||||||
|
"title": "DSpace Angular :: Metadatenreferenzliste",
|
||||||
|
"head": "Metadatenreferenzliste",
|
||||||
|
"description": "Die Metadatenreferenzliste beinhaltet alle Metadatenfelder, die zur Verfügung stehen. Die Felder können in unterschiedlichen Schemata enthalten sein. Nichtsdestotrotz benötigt DSpace mindestens qualifiziertes Dublin Core.",
|
||||||
|
"schemas": {
|
||||||
|
"table": {
|
||||||
|
"id": "ID",
|
||||||
|
"namespace": "Namensraum",
|
||||||
|
"name": "Name"
|
||||||
|
},
|
||||||
|
"no-items": "Es gbit keine Metadatenschemata."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"title": "DSpace Angular :: Referenzliste der Metadatenschemata",
|
||||||
|
"head": "Metadatenschemata",
|
||||||
|
"description": "Dies ist das Metadatenschema für \"{{namespace}}\".",
|
||||||
|
"fields": {
|
||||||
|
"head": "Felder in diesem Schema",
|
||||||
|
"table": {
|
||||||
|
"field": "Feld",
|
||||||
|
"scopenote": "Gültigkeitsbereich"
|
||||||
|
},
|
||||||
|
"no-items": "Es gibt keine Felder in diesem Schema."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bitstream-formats": {
|
||||||
|
"title": "DSpace Angular :: Referenzliste der Dateiformate",
|
||||||
|
"head": "Referenzliste der Dateiformate",
|
||||||
|
"description": "Diese Liste enhtält die in diesem Repositorium zulässigen Dateiformate und den jeweiligen Unterstützungsgrad.",
|
||||||
|
"formats": {
|
||||||
|
"table": {
|
||||||
|
"name": "Name",
|
||||||
|
"mimetype": "MIME Type",
|
||||||
|
"supportLevel": {
|
||||||
|
"head": "Unterstützungsgrad",
|
||||||
|
"0": "Unbekannt",
|
||||||
|
"1": "Bekannt",
|
||||||
|
"2": "Unterstützt"
|
||||||
|
},
|
||||||
|
"internal": "intern"
|
||||||
|
},
|
||||||
|
"no-items": "Es gibt keine Formate in dieser Referenzliste."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"default": "Am Laden ...",
|
||||||
|
"top-level-communities": "Die Hauptbereiche werden geladen ...",
|
||||||
|
"community": "Der Bereich wird geladen ...",
|
||||||
|
"collection": "Die Sammlung wird geladen ...",
|
||||||
|
"sub-collections": "Die untergeordneten Sammlungen werden geladen ...",
|
||||||
|
"recent-submissions": "Die aktuellsten Veröffentlichungen werden geladen ...",
|
||||||
|
"item": "Die Ressource wird geladen ...",
|
||||||
|
"objects": "Am Laden ...",
|
||||||
|
"search-results": "Die Suchergebnisse werden geladen ...",
|
||||||
|
"browse-by": "Die Ressourcen werden geladen ..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"default": "Fehler",
|
||||||
|
"top-level-communities": "Fehler beim Laden der Hauptbereiche.",
|
||||||
|
"community": "Fehler beim Laden des Bereiches.",
|
||||||
|
"collection": "Fehler beim Laden der Sammlung.",
|
||||||
|
"sub-collections": "Fehler beim Laden der untergeordneten Sammlungen.",
|
||||||
|
"recent-submissions": "Fehler beim Laden der aktuellsten Veröffentlichungen.",
|
||||||
|
"item": "Fehler beim Laden der Ressource.",
|
||||||
|
"objects": "Fehler beim Laden der Objekte.",
|
||||||
|
"search-results": "Fehler beim Laden der Suchergebnisse.",
|
||||||
|
"browse-by": "Fehler beim Laden der Ressourcen",
|
||||||
|
"validation": {
|
||||||
|
"pattern": "Die Eingabe kann nur folgendes Muster haben: {{ pattern }}.",
|
||||||
|
"license": {
|
||||||
|
"notgranted": "Sie müssen der Lizenz zustimmen, um die Ressource einzureichen. Wenn dies zur Zeit nicht geht, können Sie die Einreichung speichern und später wiederaufnehmen oder löschen."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"submit": "Los",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"search": "Suchen",
|
||||||
|
"remove": "Löschen",
|
||||||
|
"first-name": "Vorname",
|
||||||
|
"last-name": "Nachname",
|
||||||
|
"loading": "Am Laden ...",
|
||||||
|
"no-results": "Keine Ergebnisse gefunden",
|
||||||
|
"no-value": "Kein Wert eingegeben",
|
||||||
|
"group-collapse": "Weniger",
|
||||||
|
"group-expand": "Mehr",
|
||||||
|
"group-collapse-help": "Hier klicken, um die Anzeige zu reduzieren",
|
||||||
|
"group-expand-help": "Hier klicken, um mehr Elemente anzuzeigen"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Einloggen",
|
||||||
|
"form": {
|
||||||
|
"header": "Bitte Loggen Sie sich ein.",
|
||||||
|
"email": "E-Mail-Adresse",
|
||||||
|
"forgot-password": "Haben Sie Ihr Passwort vergessen?",
|
||||||
|
"new-user": "Sind Sie neu hier? Klicken Sie hier, um sich zu registrieren.",
|
||||||
|
"password": "Passwort",
|
||||||
|
"submit": "Einloggen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"title": "Ausloggen",
|
||||||
|
"form": {
|
||||||
|
"header": "Ausloggen aus DSpace",
|
||||||
|
"submit": "Ausloggen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"messages": {
|
||||||
|
"expired": "Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"invalid-user": "Ungültige E-Mail-Adresse oder Passwort."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -185,6 +185,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"browse": {
|
||||||
|
"title": "Browsing {{ collection }} by {{ field }} {{ value }}"
|
||||||
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"registries": {
|
"registries": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
@@ -236,18 +239,19 @@
|
|||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
"default": "Loading...",
|
"default": "Loading...",
|
||||||
"top-level-communities": "Loading top level communities...",
|
"top-level-communities": "Loading top-level communities...",
|
||||||
"community": "Loading community...",
|
"community": "Loading community...",
|
||||||
"collection": "Loading collection...",
|
"collection": "Loading collection...",
|
||||||
"sub-collections": "Loading sub-collections...",
|
"sub-collections": "Loading sub-collections...",
|
||||||
"recent-submissions": "Loading recent submissions...",
|
"recent-submissions": "Loading recent submissions...",
|
||||||
"item": "Loading item...",
|
"item": "Loading item...",
|
||||||
"objects": "Loading...",
|
"objects": "Loading...",
|
||||||
"search-results": "Loading search results..."
|
"search-results": "Loading search results...",
|
||||||
|
"browse-by": "Loading items..."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"default": "Error",
|
"default": "Error",
|
||||||
"top-level-communities": "Error fetching top level communities",
|
"top-level-communities": "Error fetching top-level communities",
|
||||||
"community": "Error fetching community",
|
"community": "Error fetching community",
|
||||||
"collection": "Error fetching collection",
|
"collection": "Error fetching collection",
|
||||||
"sub-collections": "Error fetching sub-collections",
|
"sub-collections": "Error fetching sub-collections",
|
||||||
@@ -255,6 +259,7 @@
|
|||||||
"item": "Error fetching item",
|
"item": "Error fetching item",
|
||||||
"objects": "Error fetching objects",
|
"objects": "Error fetching objects",
|
||||||
"search-results": "Error fetching search results",
|
"search-results": "Error fetching search results",
|
||||||
|
"browse-by": "Error fetching items",
|
||||||
"validation": {
|
"validation": {
|
||||||
"pattern": "This input is restricted by the current pattern: {{ pattern }}.",
|
"pattern": "This input is restricted by the current pattern: {{ pattern }}.",
|
||||||
"license": {
|
"license": {
|
||||||
@@ -275,7 +280,7 @@
|
|||||||
"group-collapse": "Collapse",
|
"group-collapse": "Collapse",
|
||||||
"group-expand": "Expand",
|
"group-expand": "Expand",
|
||||||
"group-collapse-help": "Click here to collapse",
|
"group-collapse-help": "Click here to collapse",
|
||||||
"group-expand-help": "Click here to expand and add more element"
|
"group-expand-help": "Click here to expand and add more elements"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "Login",
|
"title": "Login",
|
||||||
@@ -300,7 +305,7 @@
|
|||||||
"expired": "Your session has expired. Please log in again."
|
"expired": "Your session has expired. Please log in again."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-user": "Invalid email or password."
|
"invalid-user": "Invalid email address or password."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
277
resources/i18n/nl.json
Normal file
277
resources/i18n/nl.json
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
{
|
||||||
|
"footer": {
|
||||||
|
"copyright": "copyright © 2002-{{ year }}",
|
||||||
|
"link.dspace": "DSpace software",
|
||||||
|
"link.duraspace": "DuraSpace"
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"page": {
|
||||||
|
"news": "Nieuws",
|
||||||
|
"license": "Licentie",
|
||||||
|
"browse": {
|
||||||
|
"recent": {
|
||||||
|
"head": "Recent toegevoegd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"community": {
|
||||||
|
"page": {
|
||||||
|
"news": "Nieuws",
|
||||||
|
"license": "Licentie"
|
||||||
|
},
|
||||||
|
"sub-collection-list": {
|
||||||
|
"head": "Collecties in deze Community"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"page": {
|
||||||
|
"author": "Auteur",
|
||||||
|
"abstract": "Abstract",
|
||||||
|
"date": "Datum",
|
||||||
|
"uri": "URI",
|
||||||
|
"files": "Bestanden",
|
||||||
|
"collections": "Collecties",
|
||||||
|
"filesection": {
|
||||||
|
"download": "Download",
|
||||||
|
"name": "Naam:",
|
||||||
|
"format": "Formaat:",
|
||||||
|
"size": "Grootte:",
|
||||||
|
"description": "Beschrijving:"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"simple": "Eenvoudige item weergave",
|
||||||
|
"full": "Volledige item weergave"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"home": "Home",
|
||||||
|
"login": "Log In",
|
||||||
|
"logout": "Log Uit"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"results-per-page": "Resultaten per pagina",
|
||||||
|
"sort-direction": "Sorteer mogelijkheden",
|
||||||
|
"showing": {
|
||||||
|
"label": "Getoonde items ",
|
||||||
|
"detail": "{{ range }} tot {{ total }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sorting": {
|
||||||
|
"score": {
|
||||||
|
"DESC": "Relevantie"
|
||||||
|
},
|
||||||
|
"dc.title": {
|
||||||
|
"ASC": "Oplopend op titel",
|
||||||
|
"DESC": "Aflopend op titel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "DSpace",
|
||||||
|
"404": {
|
||||||
|
"help": "De pagina die u zoekt kan niet gevonden worden. De pagina werd mogelijk verplaatst of verwijderd. U kan onderstaande knop gebruiken om terug naar de homepagina te gaan. ",
|
||||||
|
"page-not-found": "Pagina niet gevonden",
|
||||||
|
"link": {
|
||||||
|
"home-page": "Terug naar de homepagina"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "DSpace Angular :: Home",
|
||||||
|
"description": "",
|
||||||
|
"top-level-communities": {
|
||||||
|
"head": "Communities in DSpace",
|
||||||
|
"help": "Selecteer een community om diens collecties te verkennen."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "DSpace Angular :: Zoek",
|
||||||
|
"description": "",
|
||||||
|
"form": {
|
||||||
|
"search": "Zoek",
|
||||||
|
"search_dspace": "Zoek in DSpace"
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"head": "Zoekresultaten",
|
||||||
|
"no-results": "Er waren geen resultaten voor deze zoekopdracht"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"close": "Terug naar de resultaten",
|
||||||
|
"open": "Zoek Tools",
|
||||||
|
"results": "resultaten",
|
||||||
|
"filters": {
|
||||||
|
"title": "Filters"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Instellingen",
|
||||||
|
"sort-by": "Sorteer volgens",
|
||||||
|
"rpp": "Resultaten per pagina"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"view-switch": {
|
||||||
|
"show-list": "Toon als lijst",
|
||||||
|
"show-grid": "Toon in raster"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"head": "Filters",
|
||||||
|
"reset": "Filters verwijderen",
|
||||||
|
"applied": {
|
||||||
|
"f.author": "Auteur",
|
||||||
|
"f.dateIssued.min": "Start datum",
|
||||||
|
"f.dateIssued.max": "Eind datum",
|
||||||
|
"f.subject": "Sleutelwoord",
|
||||||
|
"f.has_content_in_original_bundle": "Heeft bestanden"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"show-more": "Toon meer",
|
||||||
|
"show-less": "Inklappen",
|
||||||
|
"author": {
|
||||||
|
"placeholder": "Auteursnaam",
|
||||||
|
"head": "Auteur"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"placeholder": "Bereik filter",
|
||||||
|
"head": "Bereik"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"placeholder": "Onderwerp",
|
||||||
|
"head": "Onderwerp"
|
||||||
|
},
|
||||||
|
"dateIssued": {
|
||||||
|
"max": {
|
||||||
|
"placeholder": "Vroegste Datum"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"placeholder": "Laatste Datum"
|
||||||
|
},
|
||||||
|
"head": "Datum"
|
||||||
|
},
|
||||||
|
"has_content_in_original_bundle": {
|
||||||
|
"head": "Heeft bestanden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browse": {
|
||||||
|
"title": "Verken {{ collection }} volgens {{ field }} {{ value }}"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"registries": {
|
||||||
|
"metadata": {
|
||||||
|
"title": "DSpace Angular :: Metadata Register",
|
||||||
|
"head": "Metadata Register",
|
||||||
|
"description": "Het metadata register omvat de lijst van alle metadata velden die beschikbaar zijn in het systeem. Deze velden kunnen verspreid zijn over verschillende metadata schema's. Het qualified Dublin Core schema (dc) is een verplicht schema en kan niet worden verwijderd.",
|
||||||
|
"schemas": {
|
||||||
|
"table": {
|
||||||
|
"id": "ID",
|
||||||
|
"namespace": "Naamruimte",
|
||||||
|
"name": "Naam"
|
||||||
|
},
|
||||||
|
"no-items": "Er kunnen geen metadata schema's getoond worden."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"title": "DSpace Angular :: Metadata Schema Register",
|
||||||
|
"head": "Metadata Schema",
|
||||||
|
"description": "Dit is het metadata schema voor \"{{namespace}}\".",
|
||||||
|
"fields": {
|
||||||
|
"head": "Schema metadata velden",
|
||||||
|
"table": {
|
||||||
|
"field": "Veld",
|
||||||
|
"scopenote": "Opmerking over bereik"
|
||||||
|
},
|
||||||
|
"no-items": "Er kunnen geen metadata velden getoond worden."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bitstream-formats": {
|
||||||
|
"title": "DSpace Angular :: Bitstream Formaat Register",
|
||||||
|
"head": "Bitstream Formaat Register",
|
||||||
|
"description": "Deze lijst van Bitstream formaten biedt informatie over de formaten die in deze repository zijn toegelaten en op welke manier ze ondersteund worden. De term Bitstream wordt in DSpace gebruikt om een bestand aan te duiden dat samen met metadata onderdeel uitmaakt van een item. De naam bitstream duidt op het feit dat het bestand achterliggend wordt opgeslaan zonder bestandsextensie.",
|
||||||
|
"formats": {
|
||||||
|
"table": {
|
||||||
|
"name": "Naam",
|
||||||
|
"mimetype": "MIME Type",
|
||||||
|
"supportLevel": {
|
||||||
|
"head": "Ondersteuning",
|
||||||
|
"0": "Onbekend",
|
||||||
|
"1": "Gekend",
|
||||||
|
"2": "Ondersteund"
|
||||||
|
},
|
||||||
|
"internal": "intern"
|
||||||
|
},
|
||||||
|
"no-items": "Er kunnen geen bitstream formaten getoond worden."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading": {
|
||||||
|
"default": "Laden...",
|
||||||
|
"top-level-communities": "Inladen van de Communities op het hoogste niveau...",
|
||||||
|
"community": "Community wordt ingeladen...",
|
||||||
|
"collection": "Collectie wordt ingeladen...",
|
||||||
|
"sub-collections": "De sub-collecties worden ingeladen...",
|
||||||
|
"recent-submissions": "Recent toegevoegde items worden ingeladen...",
|
||||||
|
"item": "Item wordt ingeladen...",
|
||||||
|
"objects": "Laden...",
|
||||||
|
"search-results": "Zoekresultaten worden ingeladen...",
|
||||||
|
"browse-by": "Items worden ingeladen..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"default": "Fout",
|
||||||
|
"top-level-communities": "Fout bij het inladen van communities op het hoogste niveau",
|
||||||
|
"community": "Fout bij het ophalen van een community",
|
||||||
|
"collection": "Fout bij het ophalen van een collectie",
|
||||||
|
"sub-collections": "Fout bij het ophalen van sub-collecties",
|
||||||
|
"recent-submissions": "Fout bij het ophalen van recent toegevoegde items",
|
||||||
|
"item": "Fout bij het ophalen van items",
|
||||||
|
"objects": "Fout bij het ophalen van objecten",
|
||||||
|
"search-results": "Fout bij het ophalen van zoekresultaten",
|
||||||
|
"browse-by": "Fout bij het ophalen van items",
|
||||||
|
"validation": {
|
||||||
|
"pattern": "Deze invoer is niet toegelaten volgens dit patroon: {{ pattern }}.",
|
||||||
|
"license": {
|
||||||
|
"notgranted": "U moet de invoerlicentie goedkeuren om de invoer af te werken. Indien u deze licentie momenteel niet kan of mag goedkeuren, kan u uw werk opslaan en de invoer later afwerken. U kan dit nieuwe item ook verwijderen indien u niet voldoet aan de vereisten van de invoer licentie."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"submit": "Verstuur",
|
||||||
|
"cancel": "Annuleer",
|
||||||
|
"search": "Zoek",
|
||||||
|
"remove": "Verwijder",
|
||||||
|
"first-name": "Voornaam",
|
||||||
|
"last-name": "Achternaam",
|
||||||
|
"loading": "Inladen...",
|
||||||
|
"no-results": "Geen resultaten gevonden",
|
||||||
|
"no-value": "Geen waarde ingevoerd",
|
||||||
|
"group-collapse": "Inklappen",
|
||||||
|
"group-expand": "Uitklappen",
|
||||||
|
"group-collapse-help": "Klik hier op in te klappen",
|
||||||
|
"group-expand-help": "Klik hier om uit te klappen en om meer onderdelen toe te voegen"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Aanmelden",
|
||||||
|
"form": {
|
||||||
|
"header": "Gelieve in te loggen in DSpace",
|
||||||
|
"email": "Email adres",
|
||||||
|
"forgot-password": "Bent u uw wachtwoord vergeten?",
|
||||||
|
"new-user": "Nieuwe gebruiker? Gelieve u hier te registreren",
|
||||||
|
"password": "Wachtwoord",
|
||||||
|
"submit": "Aanmelden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"title": "Afmelden",
|
||||||
|
"form": {
|
||||||
|
"header": "Afmelden in DSpace",
|
||||||
|
"submit": "Afmelden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"messages": {
|
||||||
|
"expired": "Uw sessie is vervallen. Gelieve opnieuw aan te melden."
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"invalid-user": "Ongeldig email adres of wachtwoord."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||||
import commonjs from 'rollup-plugin-commonjs';
|
import commonjs from 'rollup-plugin-commonjs';
|
||||||
import uglify from 'rollup-plugin-uglify'
|
import terser from 'rollup-plugin-terser'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: 'dist/client.js',
|
input: 'dist/client.js',
|
||||||
@@ -8,7 +8,6 @@ export default {
|
|||||||
file: 'dist/client.js',
|
file: 'dist/client.js',
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
},
|
},
|
||||||
sourcemap: false,
|
|
||||||
plugins: [
|
plugins: [
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
jsnext: true,
|
jsnext: true,
|
||||||
@@ -17,6 +16,6 @@ export default {
|
|||||||
commonjs({
|
commonjs({
|
||||||
include: 'node_modules/rxjs/**'
|
include: 'node_modules/rxjs/**'
|
||||||
}),
|
}),
|
||||||
uglify()
|
terser.terser()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ require('zone.js/dist/async-test');
|
|||||||
require('zone.js/dist/fake-async-test');
|
require('zone.js/dist/fake-async-test');
|
||||||
|
|
||||||
// RxJS
|
// RxJS
|
||||||
require('rxjs/Rx');
|
require('rxjs');
|
||||||
|
|
||||||
var testing = require('@angular/core/testing');
|
var testing = require('@angular/core/testing');
|
||||||
var browser = require('@angular/platform-browser-dynamic/testing');
|
var browser = require('@angular/platform-browser-dynamic/testing');
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
import { BitstreamFormatsComponent } from './bitstream-formats.component';
|
import { BitstreamFormatsComponent } from './bitstream-formats.component';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
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';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { SharedModule } from '../../../shared/shared.module';
|
|
||||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
||||||
@@ -53,7 +52,7 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
extensions: null
|
extensions: null
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const mockFormats = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)));
|
const mockFormats = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)));
|
||||||
const registryServiceStub = {
|
const registryServiceStub = {
|
||||||
getBitstreamFormats: () => mockFormats
|
getBitstreamFormats: () => mockFormats
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
|
import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { MetadataRegistryComponent } from './metadata-registry.component';
|
import { MetadataRegistryComponent } from './metadata-registry.component';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
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';
|
||||||
@@ -8,7 +8,6 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { SharedModule } from '../../../shared/shared.module';
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
||||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||||
@@ -33,7 +32,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
namespace: 'http://dspace.org/mockschema'
|
namespace: 'http://dspace.org/mockschema'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const mockSchemas = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
|
const mockSchemas = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
|
||||||
const registryServiceStub = {
|
const registryServiceStub = {
|
||||||
getMetadataSchemas: () => mockSchemas
|
getMetadataSchemas: () => mockSchemas
|
||||||
};
|
};
|
||||||
@@ -68,5 +67,4 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
const mockName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
const mockName: HTMLElement = fixture.debugElement.query(By.css('#metadata-schemas tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
||||||
expect(mockName.textContent).toBe('mock');
|
expect(mockName.textContent).toBe('mock');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
import { MetadataSchemaComponent } from './metadata-schema.component';
|
import { MetadataSchemaComponent } from './metadata-schema.component';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { MockTranslateLoader } from '../../../shared/testing/mock-translate-loader';
|
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { SharedModule } from '../../../shared/shared.module';
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
||||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||||
@@ -68,15 +66,15 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
schema: mockSchemasList[1]
|
schema: mockSchemasList[1]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const mockSchemas = Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
|
const mockSchemas = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockSchemasList)));
|
||||||
const registryServiceStub = {
|
const registryServiceStub = {
|
||||||
getMetadataSchemas: () => mockSchemas,
|
getMetadataSchemas: () => mockSchemas,
|
||||||
getMetadataFieldsBySchema: (schema: MetadataSchema) => Observable.of(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema)))),
|
getMetadataFieldsBySchema: (schema: MetadataSchema) => observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema)))),
|
||||||
getMetadataSchemaByName: (schemaName: string) => Observable.of(new RemoteData(false, false, true, undefined, mockSchemasList.filter((value) => value.prefix === schemaName)[0]))
|
getMetadataSchemaByName: (schemaName: string) => observableOf(new RemoteData(false, false, true, undefined, mockSchemasList.filter((value) => value.prefix === schemaName)[0]))
|
||||||
};
|
};
|
||||||
const schemaNameParam = 'mock';
|
const schemaNameParam = 'mock';
|
||||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||||
params: Observable.of({
|
params: observableOf({
|
||||||
schemaName: schemaNameParam
|
schemaName: schemaNameParam
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { MetadataField } from '../../../core/metadata/metadatafield.model';
|
import { MetadataField } from '../../../core/metadata/metadatafield.model';
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="browse-by-author w-100 row">
|
||||||
|
<ds-browse-by class="col-xs-12 w-100"
|
||||||
|
title="{{'browse.title' | translate:{collection: '', field: 'Author', value: (value)? '"' + value + '"': ''} }}"
|
||||||
|
[objects$]="(items$ !== undefined)? items$ : authors$"
|
||||||
|
[currentUrl]="currentUrl"
|
||||||
|
[paginationConfig]="paginationConfig"
|
||||||
|
[sortConfig]="sortConfig">
|
||||||
|
</ds-browse-by>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
import {combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
|
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-author-page',
|
||||||
|
styleUrls: ['./browse-by-author-page.component.scss'],
|
||||||
|
templateUrl: './browse-by-author-page.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for browsing (items) by author (dc.contributor.author)
|
||||||
|
*/
|
||||||
|
export class BrowseByAuthorPageComponent implements OnInit {
|
||||||
|
|
||||||
|
authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
|
||||||
|
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'browse-by-author-pagination',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 20
|
||||||
|
});
|
||||||
|
sortConfig: SortOptions = new SortOptions('dc.contributor.author', SortDirection.ASC);
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
currentUrl: string;
|
||||||
|
value = '';
|
||||||
|
|
||||||
|
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute, private browseService: BrowseService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.currentUrl = this.route.snapshot.pathFromRoot
|
||||||
|
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
|
||||||
|
.join('/');
|
||||||
|
this.updatePage({
|
||||||
|
pagination: this.paginationConfig,
|
||||||
|
sort: this.sortConfig
|
||||||
|
});
|
||||||
|
this.subs.push(
|
||||||
|
observableCombineLatest(
|
||||||
|
this.route.params,
|
||||||
|
this.route.queryParams,
|
||||||
|
(params, queryParams, ) => {
|
||||||
|
return Object.assign({}, params, queryParams);
|
||||||
|
})
|
||||||
|
.subscribe((params) => {
|
||||||
|
const page = +params.page || this.paginationConfig.currentPage;
|
||||||
|
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||||
|
const sortDirection = params.sortDirection || this.sortConfig.direction;
|
||||||
|
const sortField = params.sortField || this.sortConfig.field;
|
||||||
|
this.value = +params.value || params.value || '';
|
||||||
|
const pagination = Object.assign({},
|
||||||
|
this.paginationConfig,
|
||||||
|
{ currentPage: page, pageSize: pageSize }
|
||||||
|
);
|
||||||
|
const sort = Object.assign({},
|
||||||
|
this.sortConfig,
|
||||||
|
{ direction: sortDirection, field: sortField }
|
||||||
|
);
|
||||||
|
const searchOptions = {
|
||||||
|
pagination: pagination,
|
||||||
|
sort: sort
|
||||||
|
};
|
||||||
|
if (isNotEmpty(this.value)) {
|
||||||
|
this.updatePageWithItems(searchOptions, this.value);
|
||||||
|
} else {
|
||||||
|
this.updatePage(searchOptions);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current page with searchOptions
|
||||||
|
* @param searchOptions Options to narrow down your search:
|
||||||
|
* { pagination: PaginationComponentOptions,
|
||||||
|
* sort: SortOptions }
|
||||||
|
*/
|
||||||
|
updatePage(searchOptions) {
|
||||||
|
this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions);
|
||||||
|
this.items$ = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current page with searchOptions and display items linked to author
|
||||||
|
* @param searchOptions Options to narrow down your search:
|
||||||
|
* { pagination: PaginationComponentOptions,
|
||||||
|
* sort: SortOptions }
|
||||||
|
* @param author The author's name for displaying items
|
||||||
|
*/
|
||||||
|
updatePageWithItems(searchOptions, author: string) {
|
||||||
|
this.items$ = this.browseService.getBrowseItemsFor('author', author, searchOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="browse-by-title w-100 row">
|
||||||
|
<ds-browse-by class="col-xs-12 w-100"
|
||||||
|
title="{{'browse.title' | translate:{collection: '', field: 'Title', value: ''} }}"
|
||||||
|
[objects$]="items$"
|
||||||
|
[currentUrl]="currentUrl"
|
||||||
|
[paginationConfig]="paginationConfig"
|
||||||
|
[sortConfig]="sortConfig">
|
||||||
|
</ds-browse-by>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
import {combineLatest as observableCombineLatest, Observable , Subscription } from 'rxjs';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { ActivatedRoute, PRIMARY_OUTLET, UrlSegmentGroup } from '@angular/router';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-title-page',
|
||||||
|
styleUrls: ['./browse-by-title-page.component.scss'],
|
||||||
|
templateUrl: './browse-by-title-page.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for browsing items by title (dc.title)
|
||||||
|
*/
|
||||||
|
export class BrowseByTitlePageComponent implements OnInit {
|
||||||
|
|
||||||
|
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'browse-by-title-pagination',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 20
|
||||||
|
});
|
||||||
|
sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
currentUrl: string;
|
||||||
|
|
||||||
|
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.currentUrl = this.route.snapshot.pathFromRoot
|
||||||
|
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
|
||||||
|
.join('/');
|
||||||
|
this.updatePage({
|
||||||
|
pagination: this.paginationConfig,
|
||||||
|
sort: this.sortConfig
|
||||||
|
});
|
||||||
|
this.subs.push(
|
||||||
|
observableCombineLatest(
|
||||||
|
this.route.params,
|
||||||
|
this.route.queryParams,
|
||||||
|
(params, queryParams, ) => {
|
||||||
|
return Object.assign({}, params, queryParams);
|
||||||
|
})
|
||||||
|
.subscribe((params) => {
|
||||||
|
const page = +params.page || this.paginationConfig.currentPage;
|
||||||
|
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||||
|
const sortDirection = params.sortDirection || this.sortConfig.direction;
|
||||||
|
const sortField = params.sortField || this.sortConfig.field;
|
||||||
|
const pagination = Object.assign({},
|
||||||
|
this.paginationConfig,
|
||||||
|
{ currentPage: page, pageSize: pageSize }
|
||||||
|
);
|
||||||
|
const sort = Object.assign({},
|
||||||
|
this.sortConfig,
|
||||||
|
{ direction: sortDirection, field: sortField }
|
||||||
|
);
|
||||||
|
this.updatePage({
|
||||||
|
pagination: pagination,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current page with searchOptions
|
||||||
|
* @param searchOptions Options to narrow down your search:
|
||||||
|
* { pagination: PaginationComponentOptions,
|
||||||
|
* sort: SortOptions }
|
||||||
|
*/
|
||||||
|
updatePage(searchOptions) {
|
||||||
|
this.items$ = this.itemDataService.findAll({
|
||||||
|
currentPage: searchOptions.pagination.currentPage,
|
||||||
|
elementsPerPage: searchOptions.pagination.pageSize,
|
||||||
|
sort: searchOptions.sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/app/+browse-by/browse-by-routing.module.ts
Normal file
16
src/app/+browse-by/browse-by-routing.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
|
||||||
|
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{ path: 'title', component: BrowseByTitlePageComponent },
|
||||||
|
{ path: 'author', component: BrowseByAuthorPageComponent }
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class BrowseByRoutingModule {
|
||||||
|
|
||||||
|
}
|
27
src/app/+browse-by/browse-by.module.ts
Normal file
27
src/app/+browse-by/browse-by.module.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
|
||||||
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { BrowseByRoutingModule } from './browse-by-routing.module';
|
||||||
|
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
|
||||||
|
import { BrowseService } from '../core/browse/browse.service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowseByRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
SharedModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
BrowseByTitlePageComponent,
|
||||||
|
BrowseByAuthorPageComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ItemDataService,
|
||||||
|
BrowseService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class BrowseByModule {
|
||||||
|
|
||||||
|
}
|
@@ -1,8 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
import { PaginatedList } from '../core/data/paginated-list';
|
import { PaginatedList } from '../core/data/paginated-list';
|
||||||
@@ -17,7 +15,7 @@ import { Item } from '../core/shared/item.model';
|
|||||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { filter, flatMap, map } from 'rxjs/operators';
|
import { filter, flatMap, map, tap } from 'rxjs/operators';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
import { SearchService } from '../+search-page/search-service/search.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||||
import { toDSpaceObjectListRD } from '../core/shared/operators';
|
import { toDSpaceObjectListRD } from '../core/shared/operators';
|
||||||
@@ -56,7 +54,9 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.collectionRD$ = this.route.data.map((data) => data.collection);
|
this.collectionRD$ = this.route.data.pipe(
|
||||||
|
map((data) => data.collection)
|
||||||
|
);
|
||||||
this.logoRD$ = this.collectionRD$.pipe(
|
this.logoRD$ = this.collectionRD$.pipe(
|
||||||
map((rd: RemoteData<Collection>) => rd.payload),
|
map((rd: RemoteData<Collection>) => rd.payload),
|
||||||
filter((collection: Collection) => hasValue(collection)),
|
filter((collection: Collection) => hasValue(collection)),
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Collection } from '../core/shared/collection.model';
|
import { Collection } from '../core/shared/collection.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
|
@@ -1,23 +1,14 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { ComColDataService } from '../../core/data/comcol-data.service';
|
|
||||||
import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model';
|
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { RouteService } from '../../shared/services/route.service';
|
import { RouteService } from '../../shared/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models';
|
import { Observable } from 'rxjs';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { take } from 'rxjs/operators';
|
||||||
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
|
|
||||||
import { first, flatMap, map, take } from 'rxjs/operators';
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|
||||||
import { HttpEvent } from '@angular/common/http';
|
|
||||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
|
||||||
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
|
||||||
import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-create-collection',
|
selector: 'ds-create-collection',
|
||||||
|
@@ -24,9 +24,11 @@
|
|||||||
[content]="communityPayload.copyrightText"
|
[content]="communityPayload.copyrightText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<ds-community-page-sub-collection-list [community]="communityPayload"></ds-community-page-sub-collection-list>
|
<ds-community-page-sub-collection-list
|
||||||
|
[community]="communityPayload"></ds-community-page-sub-collection-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="communityRD?.hasFailed" message="{{'error.community' | translate}}"></ds-error>
|
<ds-error *ngIf="communityRD?.hasFailed" message="{{'error.community' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="communityRD?.isLoading" message="{{'loading.community' | translate}}"></ds-loading>
|
<ds-loading *ngIf="communityRD?.isLoading"
|
||||||
|
message="{{'loading.community' | translate}}"></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
|
import { mergeMap, filter, map, first, tap } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription, Observable } from 'rxjs';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
@@ -12,7 +13,6 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
|||||||
|
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page',
|
selector: 'ds-community-page',
|
||||||
@@ -24,6 +24,8 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
export class CommunityPageComponent implements OnInit, OnDestroy {
|
export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||||
communityRD$: Observable<RemoteData<Community>>;
|
communityRD$: Observable<RemoteData<Community>>;
|
||||||
logoRD$: Observable<RemoteData<Bitstream>>;
|
logoRD$: Observable<RemoteData<Bitstream>>;
|
||||||
|
|
||||||
|
|
||||||
private subs: Subscription[] = [];
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -35,15 +37,19 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.communityRD$ = this.route.data.map((data) => data.community);
|
this.communityRD$ = this.route.data.pipe(map((data) => data.community));
|
||||||
this.logoRD$ = this.communityRD$
|
this.logoRD$ = this.communityRD$.pipe(
|
||||||
.map((rd: RemoteData<Community>) => rd.payload)
|
map((rd: RemoteData<Community>) => rd.payload),
|
||||||
.filter((community: Community) => hasValue(community))
|
filter((community: Community) => hasValue(community)),
|
||||||
.flatMap((community: Community) => community.logo);
|
mergeMap((community: Community) => community.logo));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { ErrorResponse } from '../../core/cache/response-cache.models';
|
import { Observable } from 'rxjs';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { RouteService } from '../../shared/services/route.service';
|
import { RouteService } from '../../shared/services/route.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
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';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
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';
|
||||||
@@ -17,6 +17,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [fadeInOut]
|
animations: [fadeInOut]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class TopLevelCommunityListComponent {
|
export class TopLevelCommunityListComponent {
|
||||||
communitiesRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
communitiesRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
||||||
config: PaginationComponentOptions;
|
config: PaginationComponentOptions;
|
||||||
|
@@ -6,7 +6,7 @@ import { Collection } from '../../../core/shared/collection.model';
|
|||||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||||
import { getMockRemoteDataBuildService } from '../../../shared/mocks/mock-remote-data-build.service';
|
import { getMockRemoteDataBuildService } from '../../../shared/mocks/mock-remote-data-build.service';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ const mockCollection1: Collection = Object.assign(new Collection(), {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: Observable.of(new RemoteData(false, false, true, null, mockCollection1))});
|
const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: observableOf(new RemoteData(false, false, true, null, mockCollection1))});
|
||||||
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: Observable.of(new RemoteData(false, false, false, null, mockCollection1))});
|
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: observableOf(new RemoteData(false, false, false, null, mockCollection1))});
|
||||||
|
|
||||||
describe('CollectionsComponent', () => {
|
describe('CollectionsComponent', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
@@ -35,11 +37,11 @@ export class CollectionsComponent implements OnInit {
|
|||||||
// TODO: this should use parents, but the collections
|
// TODO: this should use parents, but the collections
|
||||||
// for an Item aren't returned by the REST API yet,
|
// for an Item aren't returned by the REST API yet,
|
||||||
// only the owning collection
|
// only the owning collection
|
||||||
this.collections = this.item.owner.map((rd: RemoteData<Collection>) => [rd.payload]);
|
this.collections = this.item.owner.pipe(map((rd: RemoteData<Collection>) => [rd.payload]));
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSucceeded() {
|
hasSucceeded() {
|
||||||
return this.item.owner.map((rd: RemoteData<Collection>) => rd.hasSucceeded);
|
return this.item.owner.pipe(map((rd: RemoteData<Collection>) => rd.hasSucceeded));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
<div class="simple-view-element">
|
<div class="simple-view-element">
|
||||||
|
<span *ngIf="content.children.length != 0">
|
||||||
<h5 class="simple-view-element-header" *ngIf="label">{{ label }}</h5>
|
<h5 class="simple-view-element-header" *ngIf="label">{{ label }}</h5>
|
||||||
<div class="simple-view-element-body">
|
</span>
|
||||||
<ng-content></ng-content>
|
<div #content class="simple-view-element-body">
|
||||||
</div>
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { Component, DebugElement } from '@angular/core';
|
||||||
|
|
||||||
|
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-component-with-content',
|
||||||
|
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||||
|
' <div class="my content">\n' +
|
||||||
|
' </div>\n' +
|
||||||
|
'</ds-metadata-field-wrapper>'
|
||||||
|
})
|
||||||
|
class ContentComponent {}
|
||||||
|
|
||||||
|
describe('MetadataFieldWrapperComponent', () => {
|
||||||
|
let component: MetadataFieldWrapperComponent;
|
||||||
|
let fixture: ComponentFixture<MetadataFieldWrapperComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [MetadataFieldWrapperComponent, ContentComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MetadataFieldWrapperComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperSelector = '.simple-view-element';
|
||||||
|
const labelSelector = '.simple-view-element-header';
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show a label when there is no content', () => {
|
||||||
|
component.label = 'test label';
|
||||||
|
fixture.detectChanges();
|
||||||
|
const debugLabel = fixture.debugElement.query(By.css(labelSelector));
|
||||||
|
expect(debugLabel).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a label when there is content', () => {
|
||||||
|
const parentFixture = TestBed.createComponent(ContentComponent);
|
||||||
|
parentFixture.detectChanges();
|
||||||
|
const parentComponent = parentFixture.componentInstance;
|
||||||
|
const parentNative = parentFixture.nativeElement;
|
||||||
|
const nativeLabel = parentNative.querySelector(labelSelector);
|
||||||
|
expect(nativeLabel.textContent).toContain('test label');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
||||||
@@ -11,7 +12,7 @@ import { Component, Input } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class MetadataValuesComponent {
|
export class MetadataValuesComponent {
|
||||||
|
|
||||||
@Input() values: any;
|
@Input() values: Metadatum[];
|
||||||
|
|
||||||
@Input() separator: string;
|
@Input() separator: string;
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
|
|
||||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
|
import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the file section of the item
|
* This component renders the file section of the item
|
||||||
@@ -33,7 +33,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
|
|||||||
initialize(): void {
|
initialize(): void {
|
||||||
const originals = this.item.getFiles();
|
const originals = this.item.getFiles();
|
||||||
const licenses = this.item.getBitstreamsByBundleName('LICENSE');
|
const licenses = this.item.getBitstreamsByBundleName('LICENSE');
|
||||||
this.bitstreamsObs = Observable.combineLatest(originals, licenses, (o, l) => [...o, ...l]);
|
this.bitstreamsObs = observableCombineLatest(originals, licenses).pipe(map(([o, l]) => [...o, ...l]));
|
||||||
this.bitstreamsObs.subscribe(
|
this.bitstreamsObs.subscribe(
|
||||||
(files) =>
|
(files) =>
|
||||||
files.forEach(
|
files.forEach(
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
|
|
||||||
|
import {filter, map} from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { ItemPageComponent } from '../simple/item-page.component';
|
import { ItemPageComponent } from '../simple/item-page.component';
|
||||||
import { Metadatum } from '../../core/shared/metadatum.model';
|
import { Metadatum } from '../../core/shared/metadatum.model';
|
||||||
@@ -41,9 +43,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
|||||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.metadata$ = this.itemRD$
|
this.metadata$ = this.itemRD$.pipe(
|
||||||
.map((rd: RemoteData<Item>) => rd.payload)
|
map((rd: RemoteData<Item>) => rd.payload),
|
||||||
.filter((item: Item) => hasValue(item))
|
filter((item: Item) => hasValue(item)),
|
||||||
.map((item: Item) => item.metadata);
|
map((item: Item) => item.metadata),);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
import { ItemDataService } from '../core/data/item-data.service';
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
|
|
||||||
|
import {mergeMap, filter, map} from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
@@ -44,11 +46,11 @@ export class ItemPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.map((data) => data.item);
|
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
||||||
this.metadataService.processRemoteData(this.itemRD$);
|
this.metadataService.processRemoteData(this.itemRD$);
|
||||||
this.thumbnail$ = this.itemRD$
|
this.thumbnail$ = this.itemRD$.pipe(
|
||||||
.map((rd: RemoteData<Item>) => rd.payload)
|
map((rd: RemoteData<Item>) => rd.payload),
|
||||||
.filter((item: Item) => hasValue(item))
|
filter((item: Item) => hasValue(item)),
|
||||||
.flatMap((item: Item) => item.getThumbnail());
|
mergeMap((item: Item) => item.getThumbnail()),);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
|
|
||||||
import { LoginPageComponent } from './login-page.component';
|
import { LoginPageComponent } from './login-page.component';
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ describe('LoginPageComponent', () => {
|
|||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
select: observableOf(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import 'rxjs/add/observable/of';
|
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
|
@@ -7,7 +7,7 @@ import { SearchFilterConfig } from '../../../search-service/search-filter-config
|
|||||||
import { FilterType } from '../../../search-service/filter-type.model';
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -54,9 +54,9 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
let filterService;
|
let filterService;
|
||||||
let searchService;
|
let searchService;
|
||||||
let router;
|
let router;
|
||||||
const page = Observable.of(0);
|
const page = observableOf(0);
|
||||||
|
|
||||||
const mockValues = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
|
const mockValues = observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
@@ -65,11 +65,11 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
{ provide: Router, useValue: new RouterStub() },
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() },
|
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() },
|
||||||
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
{ provide: RemoteDataBuildService, useValue: {aggregate: () => observableOf({})} },
|
||||||
{ provide: SearchConfigurationService, useValue: {searchOptions: Observable.of({})} },
|
{ provide: SearchConfigurationService, useValue: {searchOptions: observableOf({})} },
|
||||||
{
|
{
|
||||||
provide: SearchFilterService, useValue: {
|
provide: SearchFilterService, useValue: {
|
||||||
getSelectedValuesForFilter: () => Observable.of(selectedValues),
|
getSelectedValuesForFilter: () => observableOf(selectedValues),
|
||||||
isFilterActiveWithValue: (paramName: string, filterValue: string) => true,
|
isFilterActiveWithValue: (paramName: string, filterValue: string) => true,
|
||||||
getPage: (paramName: string) => page,
|
getPage: (paramName: string) => page,
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
|
@@ -1,23 +1,26 @@
|
|||||||
|
import {
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
of as observableOf,
|
||||||
|
BehaviorSubject,
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
|
import { switchMap, distinctUntilChanged, first, map } from 'rxjs/operators';
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { Subject } from 'rxjs/Subject';
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.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 { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
|
import { EmphasizePipe } from '../../../../shared/utils/emphasize.pipe';
|
||||||
import { SearchOptions } from '../../../search-options.model';
|
|
||||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
||||||
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
@@ -56,7 +59,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Emits the result values for this filter found by the current filter query
|
* Emits the result values for this filter found by the current filter query
|
||||||
*/
|
*/
|
||||||
filterSearchResults: Observable<any[]> = Observable.of([]);
|
filterSearchResults: Observable<any[]> = observableOf([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the active values for this filter
|
* Emits the active values for this filter
|
||||||
@@ -82,25 +85,28 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
||||||
this.currentPage = this.getCurrentPage().distinctUntilChanged();
|
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
|
||||||
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
const searchOptions = this.searchConfigService.searchOptions;
|
const searchOptions = this.searchConfigService.searchOptions;
|
||||||
this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList()));
|
this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList()));
|
||||||
const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => {
|
const facetValues = observableCombineLatest(searchOptions, this.currentPage).pipe(
|
||||||
return { options, page }
|
map(([options, page]) => {
|
||||||
}).switchMap(({ options, page }) => {
|
return { options, page }
|
||||||
return this.searchService.getFacetValuesFor(this.filterConfig, page, options)
|
}),
|
||||||
.pipe(
|
switchMap(({ options, page }) => {
|
||||||
getSucceededRemoteData(),
|
return this.searchService.getFacetValuesFor(this.filterConfig, page, options)
|
||||||
map((results) => {
|
.pipe(
|
||||||
return {
|
getSucceededRemoteData(),
|
||||||
values: Observable.of(results),
|
map((results) => {
|
||||||
page: page
|
return {
|
||||||
};
|
values: observableOf(results),
|
||||||
}
|
page: page
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
});
|
);
|
||||||
let filterValues = [];
|
let filterValues = [];
|
||||||
this.subs.push(facetValues.subscribe((facetOutcome) => {
|
this.subs.push(facetValues.subscribe((facetOutcome) => {
|
||||||
const newValues$ = facetOutcome.values;
|
const newValues$ = facetOutcome.values;
|
||||||
@@ -120,7 +126,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.animationState = 'ready';
|
this.animationState = 'ready';
|
||||||
this.filterValues$.next(rd);
|
this.filterValues$.next(rd);
|
||||||
}));
|
}));
|
||||||
this.subs.push(newValues$.first().subscribe((rd) => {
|
this.subs.push(newValues$.pipe(first()).subscribe((rd) => {
|
||||||
this.isLastPage$.next(hasNoValue(rd.payload.next))
|
this.isLastPage$.next(hasNoValue(rd.payload.next))
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
@@ -183,7 +189,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
* @param data The string from the input field
|
* @param data The string from the input field
|
||||||
*/
|
*/
|
||||||
onSubmit(data: any) {
|
onSubmit(data: any) {
|
||||||
this.selectedValues.first().subscribe((selectedValues) => {
|
this.selectedValues.pipe(first()).subscribe((selectedValues) => {
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
this.router.navigate([this.getSearchLink()], {
|
this.router.navigate([this.getSearchLink()], {
|
||||||
queryParams:
|
queryParams:
|
||||||
@@ -192,7 +198,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.filter = '';
|
this.filter = '';
|
||||||
}
|
}
|
||||||
this.filterSearchResults = Observable.of([]);
|
this.filterSearchResults = observableOf([]);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -214,12 +220,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
* @returns {Observable<any>} The changed filter parameters
|
* @returns {Observable<any>} The changed filter parameters
|
||||||
*/
|
*/
|
||||||
getRemoveParams(value: string): Observable<any> {
|
getRemoveParams(value: string): Observable<any> {
|
||||||
return this.selectedValues.map((selectedValues) => {
|
return this.selectedValues.pipe(map((selectedValues) => {
|
||||||
return {
|
return {
|
||||||
[this.filterConfig.paramName]: selectedValues.filter((v) => v !== value),
|
[this.filterConfig.paramName]: selectedValues.filter((v) => v !== value),
|
||||||
page: 1
|
page: 1
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,12 +234,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
* @returns {Observable<any>} The changed filter parameters
|
* @returns {Observable<any>} The changed filter parameters
|
||||||
*/
|
*/
|
||||||
getAddParams(value: string): Observable<any> {
|
getAddParams(value: string): Observable<any> {
|
||||||
return this.selectedValues.map((selectedValues) => {
|
return this.selectedValues.pipe(map((selectedValues) => {
|
||||||
return {
|
return {
|
||||||
[this.filterConfig.paramName]: [...selectedValues, value],
|
[this.filterConfig.paramName]: [...selectedValues, value],
|
||||||
page: 1
|
page: 1
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,7 +258,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
findSuggestions(data): void {
|
findSuggestions(data): void {
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
this.searchConfigService.searchOptions.first().subscribe(
|
this.searchConfigService.searchOptions.pipe(first()).subscribe(
|
||||||
(options) => {
|
(options) => {
|
||||||
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
|
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -267,7 +273,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.filterSearchResults = Observable.of([]);
|
this.filterSearchResults = observableOf([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
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 { Observable } from 'rxjs/Observable';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { SearchFilterService } from './search-filter.service';
|
import { SearchFilterService } from './search-filter.service';
|
||||||
import { SearchService } from '../../search-service/search.service';
|
import { SearchService } from '../../search-service/search.service';
|
||||||
@@ -38,19 +38,19 @@ describe('SearchFilterComponent', () => {
|
|||||||
initialExpand: (filter) => {
|
initialExpand: (filter) => {
|
||||||
},
|
},
|
||||||
getSelectedValuesForFilter: (filter) => {
|
getSelectedValuesForFilter: (filter) => {
|
||||||
return Observable.of([filterName1, filterName2, filterName3])
|
return observableOf([filterName1, filterName2, filterName3])
|
||||||
},
|
},
|
||||||
isFilterActive: (filter) => {
|
isFilterActive: (filter) => {
|
||||||
return Observable.of([filterName1, filterName2, filterName3].indexOf(filter) >= 0);
|
return observableOf([filterName1, filterName2, filterName3].indexOf(filter) >= 0);
|
||||||
},
|
},
|
||||||
isCollapsed: (filter) => {
|
isCollapsed: (filter) => {
|
||||||
return Observable.of(true)
|
return observableOf(true)
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
|
|
||||||
};
|
};
|
||||||
let filterService;
|
let filterService;
|
||||||
const mockResults = Observable.of(['test', 'data']);
|
const mockResults = observableOf(['test', 'data']);
|
||||||
const searchServiceStub = {
|
const searchServiceStub = {
|
||||||
getFacetValuesFor: (filter) => mockResults
|
getFacetValuesFor: (filter) => mockResults
|
||||||
};
|
};
|
||||||
@@ -140,7 +140,7 @@ describe('SearchFilterComponent', () => {
|
|||||||
describe('when isCollapsed is called and the filter is collapsed', () => {
|
describe('when isCollapsed is called and the filter is collapsed', () => {
|
||||||
let isActive: Observable<boolean>;
|
let isActive: Observable<boolean>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
filterService.isCollapsed = () => Observable.of(true);
|
filterService.isCollapsed = () => observableOf(true);
|
||||||
isActive = comp.isCollapsed();
|
isActive = comp.isCollapsed();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ describe('SearchFilterComponent', () => {
|
|||||||
describe('when isCollapsed is called and the filter is not collapsed', () => {
|
describe('when isCollapsed is called and the filter is not collapsed', () => {
|
||||||
let isActive: Observable<boolean>;
|
let isActive: Observable<boolean>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
filterService.isCollapsed = () => Observable.of(false);
|
filterService.isCollapsed = () => observableOf(false);
|
||||||
isActive = comp.isCollapsed();
|
isActive = comp.isCollapsed();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
|
|
||||||
|
import {first} from 'rxjs/operators';
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { SearchFilterService } from './search-filter.service';
|
import { SearchFilterService } from './search-filter.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { slide } from '../../../shared/animations/slide';
|
import { slide } from '../../../shared/animations/slide';
|
||||||
import { isNotEmpty } from '../../../shared/empty.util';
|
import { isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
* Else, the filter should initially be collapsed
|
* Else, the filter should initially be collapsed
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getSelectedValues().first().subscribe((isActive) => {
|
this.getSelectedValues().pipe(first()).subscribe((isActive) => {
|
||||||
if (this.filter.isOpenByDefault || isNotEmpty(isActive)) {
|
if (this.filter.isOpenByDefault || isNotEmpty(isActive)) {
|
||||||
this.initialExpand();
|
this.initialExpand();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,16 +1,20 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { SearchFilterService } from './search-filter.service';
|
import { SearchFilterService } from './search-filter.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
SearchFilterCollapseAction, SearchFilterDecrementPageAction, SearchFilterExpandAction,
|
SearchFilterCollapseAction,
|
||||||
|
SearchFilterDecrementPageAction,
|
||||||
|
SearchFilterExpandAction,
|
||||||
SearchFilterIncrementPageAction,
|
SearchFilterIncrementPageAction,
|
||||||
SearchFilterInitialCollapseAction, SearchFilterInitialExpandAction, SearchFilterResetPageAction,
|
SearchFilterInitialCollapseAction,
|
||||||
|
SearchFilterInitialExpandAction,
|
||||||
|
SearchFilterResetPageAction,
|
||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from './search-filter.actions';
|
} from './search-filter.actions';
|
||||||
import { SearchFiltersState } from './search-filter.reducer';
|
import { SearchFiltersState } from './search-filter.reducer';
|
||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { FilterType } from '../../search-service/filter-type.model';
|
import { FilterType } from '../../search-service/filter-type.model';
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchFilterService', () => {
|
describe('SearchFilterService', () => {
|
||||||
let service: SearchFilterService;
|
let service: SearchFilterService;
|
||||||
@@ -28,7 +32,7 @@ describe('SearchFilterService', () => {
|
|||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
select: observableOf(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
const routeServiceStub: any = {
|
const routeServiceStub: any = {
|
||||||
@@ -42,10 +46,10 @@ describe('SearchFilterService', () => {
|
|||||||
addQueryParameterValue: (param: string, value: string) => {
|
addQueryParameterValue: (param: string, value: string) => {
|
||||||
},
|
},
|
||||||
getQueryParameterValues: (param: string) => {
|
getQueryParameterValues: (param: string) => {
|
||||||
return Observable.of({});
|
return observableOf({});
|
||||||
},
|
},
|
||||||
getQueryParamsWithPrefix: (param: string) => {
|
getQueryParamsWithPrefix: (param: string) => {
|
||||||
return Observable.of({});
|
return observableOf({});
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
};
|
};
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { Injectable, InjectionToken } from '@angular/core';
|
import { Injectable, InjectionToken } from '@angular/core';
|
||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
||||||
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import {
|
import {
|
||||||
SearchFilterCollapseAction,
|
SearchFilterCollapseAction,
|
||||||
SearchFilterDecrementPageAction,
|
SearchFilterDecrementPageAction,
|
||||||
@@ -13,14 +13,10 @@ import {
|
|||||||
SearchFilterResetPageAction,
|
SearchFilterResetPageAction,
|
||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from './search-filter.actions';
|
} from './search-filter.actions';
|
||||||
import { hasValue, isEmpty, isNotEmpty, } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
|
||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { RouteService } from '../../../shared/services/route.service';
|
import { RouteService } from '../../../shared/services/route.service';
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
import { Params } from '@angular/router';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SearchOptions } from '../../search-options.model';
|
|
||||||
import { PaginatedSearchOptions } from '../../paginated-search-options.model';
|
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
|
||||||
|
|
||||||
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||||
|
|
||||||
@@ -63,13 +59,19 @@ export class SearchFilterService {
|
|||||||
*/
|
*/
|
||||||
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
||||||
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
|
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
|
||||||
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').map((params: Params) => [].concat(...Object.values(params)));
|
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(
|
||||||
return Observable.combineLatest(values$, prefixValues$, (values, prefixValues) => {
|
map((params: Params) => [].concat(...Object.values(params)))
|
||||||
if (isNotEmpty(values)) {
|
);
|
||||||
return values;
|
|
||||||
}
|
return observableCombineLatest(values$, prefixValues$).pipe(
|
||||||
return prefixValues;
|
map(([values, prefixValues]) => {
|
||||||
})
|
if (isNotEmpty(values)) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
return prefixValues;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,14 +80,16 @@ export class SearchFilterService {
|
|||||||
* @returns {Observable<boolean>} Emits the current collapsed state of the given filter, if it's unavailable, return false
|
* @returns {Observable<boolean>} Emits the current collapsed state of the given filter, if it's unavailable, return false
|
||||||
*/
|
*/
|
||||||
isCollapsed(filterName: string): Observable<boolean> {
|
isCollapsed(filterName: string): Observable<boolean> {
|
||||||
return this.store.select(filterByNameSelector(filterName))
|
return this.store.pipe(
|
||||||
.map((object: SearchFilterState) => {
|
select(filterByNameSelector(filterName)),
|
||||||
|
map((object: SearchFilterState) => {
|
||||||
if (object) {
|
if (object) {
|
||||||
return object.filterCollapsed;
|
return object.filterCollapsed;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,14 +98,15 @@ export class SearchFilterService {
|
|||||||
* @returns {Observable<boolean>} Emits the current page state of the given filter, if it's unavailable, return 1
|
* @returns {Observable<boolean>} Emits the current page state of the given filter, if it's unavailable, return 1
|
||||||
*/
|
*/
|
||||||
getPage(filterName: string): Observable<number> {
|
getPage(filterName: string): Observable<number> {
|
||||||
return this.store.select(filterByNameSelector(filterName))
|
return this.store.pipe(
|
||||||
.map((object: SearchFilterState) => {
|
select(filterByNameSelector(filterName)),
|
||||||
|
map((object: SearchFilterState) => {
|
||||||
if (object) {
|
if (object) {
|
||||||
return object.page;
|
return object.page;
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,6 +164,7 @@ export class SearchFilterService {
|
|||||||
public incrementPage(filterName: string): void {
|
public incrementPage(filterName: string): void {
|
||||||
this.store.dispatch(new SearchFilterIncrementPageAction(filterName));
|
this.store.dispatch(new SearchFilterIncrementPageAction(filterName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a reset page action to the store for a given filter
|
* Dispatches a reset page action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {string} filterName The filter for which the action is dispatched
|
||||||
|
@@ -7,7 +7,7 @@ import { SearchFilterConfig } from '../../../search-service/search-filter-config
|
|||||||
import { FilterType } from '../../../search-service/filter-type.model';
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs'
|
||||||
import { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -56,13 +56,13 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const searchLink = '/search';
|
const searchLink = '/search';
|
||||||
const selectedValues = Observable.of([value1]);
|
const selectedValues = observableOf([value1]);
|
||||||
let filterService;
|
let filterService;
|
||||||
let searchService;
|
let searchService;
|
||||||
let router;
|
let router;
|
||||||
const page = Observable.of(0);
|
const page = observableOf(0);
|
||||||
|
|
||||||
const mockValues = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
|
const mockValues = observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), values)));
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
@@ -71,10 +71,10 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
{ provide: Router, useValue: new RouterStub() },
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
{ provide: FILTER_CONFIG, useValue: mockFilterConfig },
|
{ provide: FILTER_CONFIG, useValue: mockFilterConfig },
|
||||||
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
{ provide: RemoteDataBuildService, useValue: {aggregate: () => observableOf({})} },
|
||||||
{ provide: RouteService, useValue: {getQueryParameterValue: () => Observable.of({})} },
|
{ provide: RouteService, useValue: {getQueryParameterValue: () => observableOf({})} },
|
||||||
{ provide: SearchConfigurationService, useValue: {
|
{ provide: SearchConfigurationService, useValue: {
|
||||||
searchOptions: Observable.of({}) }
|
searchOptions: observableOf({}) }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchFilterService, useValue: {
|
provide: SearchFilterService, useValue: {
|
||||||
|
@@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
of as observableOf,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
Observable,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
@@ -12,10 +19,8 @@ import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
|||||||
import { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { RouteService } from '../../../../shared/services/route.service';
|
import { RouteService } from '../../../../shared/services/route.service';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,13 +85,15 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
|
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
|
||||||
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
|
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
|
||||||
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).startWith(undefined);
|
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).pipe(startWith(undefined));
|
||||||
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).startWith(undefined);
|
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).pipe(startWith(undefined));
|
||||||
this.sub = Observable.combineLatest(iniMin, iniMax, (min, max) => {
|
this.sub = observableCombineLatest(iniMin, iniMax).pipe(
|
||||||
const minimum = hasValue(min) ? min : this.min;
|
map(([min, max]) => {
|
||||||
const maximum = hasValue(max) ? max : this.max;
|
const minimum = hasValue(min) ? min : this.min;
|
||||||
return [minimum, maximum]
|
const maximum = hasValue(max) ? max : this.max;
|
||||||
}).subscribe((minmax) => this.range = minmax);
|
return [minimum, maximum]
|
||||||
|
})
|
||||||
|
).subscribe((minmax) => this.range = minmax);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,7 +105,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
const parts = value.split(rangeDelimiter);
|
const parts = value.split(rangeDelimiter);
|
||||||
const min = parts.length > 1 ? parts[0].trim() : value;
|
const min = parts.length > 1 ? parts[0].trim() : value;
|
||||||
const max = parts.length > 1 ? parts[1].trim() : value;
|
const max = parts.length > 1 ? parts[1].trim() : value;
|
||||||
return Observable.of(
|
return observableOf(
|
||||||
{
|
{
|
||||||
[this.filterConfig.paramName + minSuffix]: [min],
|
[this.filterConfig.paramName + minSuffix]: [min],
|
||||||
[this.filterConfig.paramName + maxSuffix]: [max],
|
[this.filterConfig.paramName + maxSuffix]: [max],
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { FilterType } from '../../../search-service/filter-type.model';
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
import {
|
import {
|
||||||
facetLoad,
|
facetLoad,
|
||||||
|
@@ -7,8 +7,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { SearchFilterService } from './search-filter/search-filter.service';
|
import { SearchFilterService } from './search-filter/search-filter.service';
|
||||||
import { SearchFiltersComponent } from './search-filters.component';
|
import { SearchFiltersComponent } from './search-filters.component';
|
||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchFiltersComponent', () => {
|
describe('SearchFiltersComponent', () => {
|
||||||
let comp: SearchFiltersComponent;
|
let comp: SearchFiltersComponent;
|
||||||
@@ -17,7 +17,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
const searchServiceStub = {
|
const searchServiceStub = {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
getConfig: () =>
|
getConfig: () =>
|
||||||
Observable.of({ hasSucceeded: true, payload: [] }),
|
observableOf({ hasSucceeded: true, payload: [] }),
|
||||||
getClearFiltersQueryParams: () => {
|
getClearFiltersQueryParams: () => {
|
||||||
},
|
},
|
||||||
getSearchLink: () => {
|
getSearchLink: () => {
|
||||||
@@ -31,7 +31,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', {
|
const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', {
|
||||||
getCurrentFrontendFilters: Observable.of({})
|
getCurrentFrontendFilters: observableOf({})
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { filter, map, mergeMap, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { SearchFilterService } from './search-filter/search-filter.service';
|
import { SearchFilterService } from './search-filter/search-filter.service';
|
||||||
@@ -37,10 +39,10 @@ export class SearchFiltersComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) {
|
constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) {
|
||||||
this.filters = searchService.getConfig().pipe(getSucceededRemoteData());
|
this.filters = searchService.getConfig().pipe(getSucceededRemoteData());
|
||||||
this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => {
|
this.clearParams = searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => {
|
||||||
Object.keys(filters).forEach((f) => filters[f] = null);
|
Object.keys(filters).forEach((f) => filters[f] = null);
|
||||||
return filters;
|
return filters;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,23 +57,22 @@ export class SearchFiltersComponent {
|
|||||||
* @param {SearchFilterConfig} filter The filter to check for
|
* @param {SearchFilterConfig} filter The filter to check for
|
||||||
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||||
*/
|
*/
|
||||||
isActive(filter: SearchFilterConfig): Observable<boolean> {
|
isActive(filterConfig: SearchFilterConfig): Observable<boolean> {
|
||||||
// console.log(filter.name);
|
return this.filterService.getSelectedValuesForFilter(filterConfig).pipe(
|
||||||
return this.filterService.getSelectedValuesForFilter(filter)
|
mergeMap((isActive) => {
|
||||||
.flatMap((isActive) => {
|
|
||||||
if (isNotEmpty(isActive)) {
|
if (isNotEmpty(isActive)) {
|
||||||
return Observable.of(true);
|
return observableOf(true);
|
||||||
} else {
|
} else {
|
||||||
return this.searchConfigService.searchOptions
|
return this.searchConfigService.searchOptions.pipe(
|
||||||
.switchMap((options) => {
|
switchMap((options) => {
|
||||||
return this.searchService.getFacetValuesFor(filter, 1, options)
|
return this.searchService.getFacetValuesFor(filterConfig, 1, options).pipe(
|
||||||
.filter((RD) => !RD.isLoading)
|
filter((RD) => !RD.isLoading),
|
||||||
.map((valuesRD) => {
|
map((valuesRD) => {
|
||||||
return valuesRD.payload.totalElements > 0
|
return valuesRD.payload.totalElements > 0
|
||||||
})
|
}),)
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}).startWith(true);
|
}),startWith(true),);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { SearchService } from '../search-service/search.service';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
|
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
|
||||||
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
@@ -35,7 +35,7 @@ describe('SearchLabelsComponent', () => {
|
|||||||
declarations: [SearchLabelsComponent, ObjectKeysPipe],
|
declarations: [SearchLabelsComponent, ObjectKeysPipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
{ provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => Observable.of({})} }
|
{ provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(SearchLabelsComponent, {
|
}).overrideComponent(SearchLabelsComponent, {
|
||||||
@@ -47,7 +47,7 @@ describe('SearchLabelsComponent', () => {
|
|||||||
fixture = TestBed.createComponent(SearchLabelsComponent);
|
fixture = TestBed.createComponent(SearchLabelsComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
searchService = (comp as any).searchService;
|
searchService = (comp as any).searchService;
|
||||||
(comp as any).appliedFilters = Observable.of(mockFilters);
|
(comp as any).appliedFilters = observableOf(mockFilters);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import 'rxjs/add/observable/of';
|
|
||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
import { SearchOptions } from './search-options.model';
|
import { SearchOptions } from './search-options.model';
|
||||||
import { SearchFilter } from './search-filter.model';
|
import { SearchFilter } from './search-filter.model';
|
||||||
|
@@ -5,8 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
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 { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
@@ -30,18 +29,18 @@ describe('SearchPageComponent', () => {
|
|||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
select: observableOf(true)
|
||||||
});
|
});
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
pagination.id = 'search-results-pagination';
|
pagination.id = 'search-results-pagination';
|
||||||
pagination.currentPage = 1;
|
pagination.currentPage = 1;
|
||||||
pagination.pageSize = 10;
|
pagination.pageSize = 10;
|
||||||
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
||||||
const mockResults = Observable.of(new RemoteData(false, false, true, null, ['test', 'data']));
|
const mockResults = observableOf(new RemoteData(false, false, true, null, ['test', 'data']));
|
||||||
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
||||||
search: mockResults,
|
search: mockResults,
|
||||||
getSearchLink: '/search',
|
getSearchLink: '/search',
|
||||||
getScopes: Observable.of(['test-scope'])
|
getScopes: observableOf(['test-scope'])
|
||||||
});
|
});
|
||||||
const queryParam = 'test query';
|
const queryParam = 'test query';
|
||||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||||
@@ -52,15 +51,15 @@ describe('SearchPageComponent', () => {
|
|||||||
sort
|
sort
|
||||||
};
|
};
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
queryParams: Observable.of({
|
queryParams: observableOf({
|
||||||
query: queryParam,
|
query: queryParam,
|
||||||
scope: scopeParam
|
scope: scopeParam
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
const sidebarService = {
|
const sidebarService = {
|
||||||
isCollapsed: Observable.of(true),
|
isCollapsed: observableOf(true),
|
||||||
collapse: () => this.isCollapsed = Observable.of(true),
|
collapse: () => this.isCollapsed = observableOf(true),
|
||||||
expand: () => this.isCollapsed = Observable.of(false)
|
expand: () => this.isCollapsed = observableOf(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@@ -80,9 +79,9 @@ describe('SearchPageComponent', () => {
|
|||||||
{
|
{
|
||||||
provide: HostWindowService, useValue: jasmine.createSpyObj('hostWindowService',
|
provide: HostWindowService, useValue: jasmine.createSpyObj('hostWindowService',
|
||||||
{
|
{
|
||||||
isXs: Observable.of(true),
|
isXs: observableOf(true),
|
||||||
isSm: Observable.of(false),
|
isSm: observableOf(false),
|
||||||
isXsOrSm: Observable.of(true)
|
isXsOrSm: observableOf(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -98,7 +97,7 @@ describe('SearchPageComponent', () => {
|
|||||||
paginatedSearchOptions: hot('a', {
|
paginatedSearchOptions: hot('a', {
|
||||||
a: paginatedSearchOptions
|
a: paginatedSearchOptions
|
||||||
}),
|
}),
|
||||||
getCurrentScope: (a) => Observable.of('test-id')
|
getCurrentScope: (a) => observableOf('test-id')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -154,7 +153,7 @@ describe('SearchPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
||||||
comp.isSidebarCollapsed = () => Observable.of(true);
|
comp.isSidebarCollapsed = () => observableOf(true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,7 +168,7 @@ describe('SearchPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
||||||
comp.isSidebarCollapsed = () => Observable.of(false);
|
comp.isSidebarCollapsed = () => observableOf(false);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { flatMap, switchMap, } from 'rxjs/operators';
|
import { switchMap, } from 'rxjs/operators';
|
||||||
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 { DSpaceObject } from '../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||||
@@ -11,9 +11,7 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte
|
|||||||
import { SearchResult } from './search-result.model';
|
import { SearchResult } from './search-result.model';
|
||||||
import { SearchService } from './search-service/search.service';
|
import { SearchService } from './search-service/search.service';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
|
|
||||||
@@ -78,8 +76,8 @@ export class SearchPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||||
this.sub = this.searchOptions$
|
this.sub = this.searchOptions$.pipe(
|
||||||
.switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData()))
|
switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData())))
|
||||||
.subscribe((results) => {
|
.subscribe((results) => {
|
||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
|
@@ -3,8 +3,8 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
|||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { SearchFilter } from '../search-filter.model';
|
import { SearchFilter } from '../search-filter.model';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchConfigurationService', () => {
|
describe('SearchConfigurationService', () => {
|
||||||
let service: SearchConfigurationService;
|
let service: SearchConfigurationService;
|
||||||
@@ -24,8 +24,8 @@ describe('SearchConfigurationService', () => {
|
|||||||
const backendFilters = [new SearchFilter('f.author', ['another value']), new SearchFilter('f.date', ['[2013 TO 2018]'])];
|
const backendFilters = [new SearchFilter('f.author', ['another value']), new SearchFilter('f.date', ['[2013 TO 2018]'])];
|
||||||
|
|
||||||
const spy = jasmine.createSpyObj('RouteService', {
|
const spy = jasmine.createSpyObj('RouteService', {
|
||||||
getQueryParameterValue: Observable.of(value1),
|
getQueryParameterValue: observableOf(value1),
|
||||||
getQueryParamsWithPrefix: Observable.of(prefixFilter)
|
getQueryParamsWithPrefix: observableOf(prefixFilter)
|
||||||
});
|
});
|
||||||
|
|
||||||
const activatedRoute: any = new ActivatedRouteStub();
|
const activatedRoute: any = new ActivatedRouteStub();
|
||||||
|
@@ -1,15 +1,21 @@
|
|||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
merge as observableMerge,
|
||||||
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
|
Subscription
|
||||||
|
} from 'rxjs';
|
||||||
|
import { filter, map } from 'rxjs/operators';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SearchOptions } from '../search-options.model';
|
import { SearchOptions } from '../search-options.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import { RouteService } from '../../shared/services/route.service';
|
import { RouteService } from '../../shared/services/route.service';
|
||||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../core/shared/operators';
|
||||||
import { SearchFilter } from '../search-filter.model';
|
import { SearchFilter } from '../search-filter.model';
|
||||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||||
@@ -87,27 +93,27 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
* @returns {Observable<string>} Emits the current scope's identifier
|
* @returns {Observable<string>} Emits the current scope's identifier
|
||||||
*/
|
*/
|
||||||
getCurrentScope(defaultScope: string) {
|
getCurrentScope(defaultScope: string) {
|
||||||
return this.routeService.getQueryParameterValue('scope').map((scope) => {
|
return this.routeService.getQueryParameterValue('scope').pipe(map((scope) => {
|
||||||
return scope || defaultScope;
|
return scope || defaultScope;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current query string
|
* @returns {Observable<string>} Emits the current query string
|
||||||
*/
|
*/
|
||||||
getCurrentQuery(defaultQuery: string) {
|
getCurrentQuery(defaultQuery: string) {
|
||||||
return this.routeService.getQueryParameterValue('query').map((query) => {
|
return this.routeService.getQueryParameterValue('query').pipe(map((query) => {
|
||||||
return query || defaultQuery;
|
return query || defaultQuery;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<number>} Emits the current DSpaceObject type as a number
|
* @returns {Observable<number>} Emits the current DSpaceObject type as a number
|
||||||
*/
|
*/
|
||||||
getCurrentDSOType(): Observable<DSpaceObjectType> {
|
getCurrentDSOType(): Observable<DSpaceObjectType> {
|
||||||
return this.routeService.getQueryParameterValue('dsoType')
|
return this.routeService.getQueryParameterValue('dsoType').pipe(
|
||||||
.filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()]))
|
filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()])),
|
||||||
.map((type) => DSpaceObjectType[type.toUpperCase()]);
|
map((type) => DSpaceObjectType[type.toUpperCase()]),);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,12 +122,13 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable<PaginationComponentOptions> {
|
getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable<PaginationComponentOptions> {
|
||||||
const page$ = this.routeService.getQueryParameterValue('page');
|
const page$ = this.routeService.getQueryParameterValue('page');
|
||||||
const size$ = this.routeService.getQueryParameterValue('pageSize');
|
const size$ = this.routeService.getQueryParameterValue('pageSize');
|
||||||
return Observable.combineLatest(page$, size$, (page, size) => {
|
return observableCombineLatest(page$, size$).pipe(map(([page, size]) => {
|
||||||
return Object.assign(new PaginationComponentOptions(), defaultPagination, {
|
return Object.assign(new PaginationComponentOptions(), defaultPagination, {
|
||||||
currentPage: page || defaultPagination.currentPage,
|
currentPage: page || defaultPagination.currentPage,
|
||||||
pageSize: size || defaultPagination.pageSize
|
pageSize: size || defaultPagination.pageSize
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,7 +137,7 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> {
|
getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> {
|
||||||
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
|
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
|
||||||
const sortField$ = this.routeService.getQueryParameterValue('sortField');
|
const sortField$ = this.routeService.getQueryParameterValue('sortField');
|
||||||
return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => {
|
return observableCombineLatest(sortDirection$, sortField$).pipe(map(([sortDirection, sortField]) => {
|
||||||
// Dirty fix because sometimes the observable value is null somehow
|
// Dirty fix because sometimes the observable value is null somehow
|
||||||
sortField = this.route.snapshot.queryParamMap.get('sortField');
|
sortField = this.route.snapshot.queryParamMap.get('sortField');
|
||||||
|
|
||||||
@@ -138,20 +145,21 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
const direction = SortDirection[sortDirection] || defaultSort.direction;
|
const direction = SortDirection[sortDirection] || defaultSort.direction;
|
||||||
return new SortOptions(field, direction)
|
return new SortOptions(field, direction)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
|
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
|
||||||
*/
|
*/
|
||||||
getCurrentFilters(): Observable<SearchFilter[]> {
|
getCurrentFilters(): Observable<SearchFilter[]> {
|
||||||
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => {
|
return this.routeService.getQueryParamsWithPrefix('f.').pipe(map((filterParams) => {
|
||||||
if (isNotEmpty(filterParams)) {
|
if (isNotEmpty(filterParams)) {
|
||||||
const filters = [];
|
const filters = [];
|
||||||
Object.keys(filterParams).forEach((key) => {
|
Object.keys(filterParams).forEach((key) => {
|
||||||
if (key.endsWith('.min') || key.endsWith('.max')) {
|
if (key.endsWith('.min') || key.endsWith('.max')) {
|
||||||
const realKey = key.slice(0, -4);
|
const realKey = key.slice(0, -4);
|
||||||
if (hasNoValue(filters.find((filter) => filter.key === realKey))) {
|
if (hasNoValue(filters.find((f) => f.key === realKey))) {
|
||||||
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
|
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
|
||||||
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
|
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
|
||||||
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']']));
|
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']']));
|
||||||
@@ -163,7 +171,7 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,7 +187,7 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
* @returns {Subscription} The subscription to unsubscribe from
|
* @returns {Subscription} The subscription to unsubscribe from
|
||||||
*/
|
*/
|
||||||
subscribeToSearchOptions(defaults: SearchOptions): Subscription {
|
subscribeToSearchOptions(defaults: SearchOptions): Subscription {
|
||||||
return Observable.merge(
|
return observableMerge(
|
||||||
this.getScopePart(defaults.scope),
|
this.getScopePart(defaults.scope),
|
||||||
this.getQueryPart(defaults.query),
|
this.getQueryPart(defaults.query),
|
||||||
this.getDSOTypePart(),
|
this.getDSOTypePart(),
|
||||||
@@ -197,7 +205,7 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
* @returns {Subscription} The subscription to unsubscribe from
|
* @returns {Subscription} The subscription to unsubscribe from
|
||||||
*/
|
*/
|
||||||
subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription {
|
subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription {
|
||||||
return Observable.merge(
|
return observableMerge(
|
||||||
this.getPaginationPart(defaults.pagination),
|
this.getPaginationPart(defaults.pagination),
|
||||||
this.getSortPart(defaults.sort),
|
this.getSortPart(defaults.sort),
|
||||||
this.getScopePart(defaults.scope),
|
this.getScopePart(defaults.scope),
|
||||||
@@ -222,7 +230,7 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
scope: this.defaultScope,
|
scope: this.defaultScope,
|
||||||
query: this.defaultQuery
|
query: this.defaultQuery
|
||||||
});
|
});
|
||||||
this._defaults = Observable.of(new RemoteData(false, false, true, null, options));
|
this._defaults = observableOf(new RemoteData(false, false, true, null, options));
|
||||||
}
|
}
|
||||||
return this._defaults;
|
return this._defaults;
|
||||||
}
|
}
|
||||||
@@ -240,53 +248,53 @@ export class SearchConfigurationService implements OnDestroy {
|
|||||||
* @returns {Observable<string>} Emits the current scope's identifier
|
* @returns {Observable<string>} Emits the current scope's identifier
|
||||||
*/
|
*/
|
||||||
private getScopePart(defaultScope: string): Observable<any> {
|
private getScopePart(defaultScope: string): Observable<any> {
|
||||||
return this.getCurrentScope(defaultScope).map((scope) => {
|
return this.getCurrentScope(defaultScope).pipe(map((scope) => {
|
||||||
return { scope }
|
return { scope }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
|
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
|
||||||
*/
|
*/
|
||||||
private getQueryPart(defaultQuery: string): Observable<any> {
|
private getQueryPart(defaultQuery: string): Observable<any> {
|
||||||
return this.getCurrentQuery(defaultQuery).map((query) => {
|
return this.getCurrentQuery(defaultQuery).pipe(map((query) => {
|
||||||
return { query }
|
return { query }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
|
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
|
||||||
*/
|
*/
|
||||||
private getDSOTypePart(): Observable<any> {
|
private getDSOTypePart(): Observable<any> {
|
||||||
return this.getCurrentDSOType().map((dsoType) => {
|
return this.getCurrentDSOType().pipe(map((dsoType) => {
|
||||||
return { dsoType }
|
return { dsoType }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object
|
* @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object
|
||||||
*/
|
*/
|
||||||
private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> {
|
private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> {
|
||||||
return this.getCurrentPagination(defaultPagination).map((pagination) => {
|
return this.getCurrentPagination(defaultPagination).pipe(map((pagination) => {
|
||||||
return { pagination }
|
return { pagination }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string>} Emits the current sorting settings as a partial SearchOptions object
|
* @returns {Observable<string>} Emits the current sorting settings as a partial SearchOptions object
|
||||||
*/
|
*/
|
||||||
private getSortPart(defaultSort: SortOptions): Observable<any> {
|
private getSortPart(defaultSort: SortOptions): Observable<any> {
|
||||||
return this.getCurrentSort(defaultSort).map((sort) => {
|
return this.getCurrentSort(defaultSort).pipe(map((sort) => {
|
||||||
return { sort }
|
return { sort }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<Params>} Emits the current active filters as a partial SearchOptions object
|
* @returns {Observable<Params>} Emits the current active filters as a partial SearchOptions object
|
||||||
*/
|
*/
|
||||||
private getFiltersPart(): Observable<any> {
|
private getFiltersPart(): Observable<any> {
|
||||||
return this.getCurrentFilters().map((filters) => {
|
return this.getCurrentFilters().pipe(map((filters) => {
|
||||||
return { filters }
|
return { filters }
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,26 +8,25 @@ import { SearchService } from './search.service';
|
|||||||
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
||||||
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
|
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
import { ResponseCacheService } from '../../core/cache/response-cache.service';
|
|
||||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
import { RouterStub } from '../../shared/testing/router-stub';
|
import { RouterStub } from '../../shared/testing/router-stub';
|
||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
|
|
||||||
import { RequestEntry } from '../../core/data/request.reducer';
|
import { RequestEntry } from '../../core/data/request.reducer';
|
||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
|
|
||||||
import {
|
import {
|
||||||
FacetConfigSuccessResponse,
|
FacetConfigSuccessResponse,
|
||||||
SearchSuccessResponse
|
SearchSuccessResponse
|
||||||
} from '../../core/cache/response-cache.models';
|
} from '../../core/cache/response.models';
|
||||||
import { SearchQueryResponse } from './search-query-response.model';
|
import { SearchQueryResponse } from './search-query-response.model';
|
||||||
import { SearchFilterConfig } from './search-filter-config.model';
|
import { SearchFilterConfig } from './search-filter-config.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
class DummyComponent {
|
class DummyComponent {
|
||||||
@@ -52,12 +51,11 @@ describe('SearchService', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
{ provide: ActivatedRoute, useValue: route },
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
{ provide: ResponseCacheService, useValue: getMockResponseCacheService() },
|
|
||||||
{ provide: RequestService, useValue: getMockRequestService() },
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
{ provide: RemoteDataBuildService, useValue: {} },
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
{ provide: HALEndpointService, useValue: {} },
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
{ provide: CommunityDataService, useValue: {}},
|
{ provide: CommunityDataService, useValue: {} },
|
||||||
{ provide: DSpaceObjectDataService, useValue: {}},
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
SearchService
|
SearchService
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -84,14 +82,15 @@ describe('SearchService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const remoteDataBuildService = {
|
const remoteDataBuildService = {
|
||||||
toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, responseCacheObs: Observable<ResponseCacheEntry>, payloadObs: Observable<any>) => {
|
toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, payloadObs: Observable<any>) => {
|
||||||
return Observable.combineLatest(requestEntryObs,
|
return observableCombineLatest(requestEntryObs, payloadObs).pipe(
|
||||||
responseCacheObs, payloadObs, (req, res, pay) => {
|
map(([req, pay]) => {
|
||||||
return { req, res, pay };
|
return { req, pay };
|
||||||
});
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
aggregate: (input: Array<Observable<RemoteData<any>>>): Observable<RemoteData<any[]>> => {
|
aggregate: (input: Array<Observable<RemoteData<any>>>): Observable<RemoteData<any[]>> => {
|
||||||
return Observable.of(new RemoteData(false, false, true, null, []));
|
return observableOf(new RemoteData(false, false, true, null, []));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,12 +108,11 @@ describe('SearchService', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
{ provide: ActivatedRoute, useValue: route },
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
{ provide: ResponseCacheService, useValue: getMockResponseCacheService() },
|
|
||||||
{ provide: RequestService, useValue: getMockRequestService() },
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
||||||
{ provide: HALEndpointService, useValue: halService },
|
{ provide: HALEndpointService, useValue: halService },
|
||||||
{ provide: CommunityDataService, useValue: {}},
|
{ provide: CommunityDataService, useValue: {} },
|
||||||
{ provide: DSpaceObjectDataService, useValue: {}},
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
SearchService
|
SearchService
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -158,10 +156,8 @@ describe('SearchService', () => {
|
|||||||
const searchOptions = new PaginatedSearchOptions({});
|
const searchOptions = new PaginatedSearchOptions({});
|
||||||
const queryResponse = Object.assign(new SearchQueryResponse(), { objects: [] });
|
const queryResponse = Object.assign(new SearchQueryResponse(), { objects: [] });
|
||||||
const response = new SearchSuccessResponse(queryResponse, '200');
|
const response = new SearchSuccessResponse(queryResponse, '200');
|
||||||
const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response });
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(Observable.of(endPoint));
|
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||||
(searchService as any).responseCache.get.and.returnValue(Observable.of(responseEntry));
|
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
searchService.search(searchOptions).subscribe((t) => {
|
searchService.search(searchOptions).subscribe((t) => {
|
||||||
}); // subscribe to make sure all methods are called
|
}); // subscribe to make sure all methods are called
|
||||||
@@ -179,19 +175,14 @@ describe('SearchService', () => {
|
|||||||
it('should call getByHref on the request service with the correct request url', () => {
|
it('should call getByHref on the request service with the correct request url', () => {
|
||||||
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(endPoint);
|
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(endPoint);
|
||||||
});
|
});
|
||||||
it('should call get on the request service with the correct request url', () => {
|
|
||||||
expect((searchService as any).responseCache.get).toHaveBeenCalledWith(endPoint);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when getConfig is called without a scope', () => {
|
describe('when getConfig is called without a scope', () => {
|
||||||
const endPoint = 'http://endpoint.com/test/config';
|
const endPoint = 'http://endpoint.com/test/config';
|
||||||
const filterConfig = [new SearchFilterConfig()];
|
const filterConfig = [new SearchFilterConfig()];
|
||||||
const response = new FacetConfigSuccessResponse(filterConfig, '200');
|
const response = new FacetConfigSuccessResponse(filterConfig, '200');
|
||||||
const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response });
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(Observable.of(endPoint));
|
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||||
(searchService as any).responseCache.get.and.returnValue(Observable.of(responseEntry));
|
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
searchService.getConfig(null).subscribe((t) => {
|
searchService.getConfig(null).subscribe((t) => {
|
||||||
}); // subscribe to make sure all methods are called
|
}); // subscribe to make sure all methods are called
|
||||||
@@ -209,9 +200,6 @@ describe('SearchService', () => {
|
|||||||
it('should call getByHref on the request service with the correct request url', () => {
|
it('should call getByHref on the request service with the correct request url', () => {
|
||||||
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(endPoint);
|
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(endPoint);
|
||||||
});
|
});
|
||||||
it('should call get on the request service with the correct request url', () => {
|
|
||||||
expect((searchService as any).responseCache.get).toHaveBeenCalledWith(endPoint);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when getConfig is called with a scope', () => {
|
describe('when getConfig is called with a scope', () => {
|
||||||
@@ -220,10 +208,8 @@ describe('SearchService', () => {
|
|||||||
const requestUrl = endPoint + '?scope=' + scope;
|
const requestUrl = endPoint + '?scope=' + scope;
|
||||||
const filterConfig = [new SearchFilterConfig()];
|
const filterConfig = [new SearchFilterConfig()];
|
||||||
const response = new FacetConfigSuccessResponse(filterConfig, '200');
|
const response = new FacetConfigSuccessResponse(filterConfig, '200');
|
||||||
const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response });
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(Observable.of(endPoint));
|
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||||
(searchService as any).responseCache.get.and.returnValue(Observable.of(responseEntry));
|
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
searchService.getConfig(scope).subscribe((t) => {
|
searchService.getConfig(scope).subscribe((t) => {
|
||||||
}); // subscribe to make sure all methods are called
|
}); // subscribe to make sure all methods are called
|
||||||
@@ -241,9 +227,6 @@ describe('SearchService', () => {
|
|||||||
it('should call getByHref on the request service with the correct request url', () => {
|
it('should call getByHref on the request service with the correct request url', () => {
|
||||||
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(requestUrl);
|
expect((searchService as any).requestService.getByHref).toHaveBeenCalledWith(requestUrl);
|
||||||
});
|
});
|
||||||
it('should call get on the request service with the correct request url', () => {
|
|
||||||
expect((searchService as any).responseCache.get).toHaveBeenCalledWith(requestUrl);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute,
|
ActivatedRoute,
|
||||||
@@ -6,16 +7,13 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
UrlSegmentGroup
|
UrlSegmentGroup
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { flatMap, map, switchMap } from 'rxjs/operators';
|
|
||||||
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
||||||
import {
|
import {
|
||||||
FacetConfigSuccessResponse,
|
FacetConfigSuccessResponse,
|
||||||
FacetValueSuccessResponse,
|
FacetValueSuccessResponse,
|
||||||
SearchSuccessResponse
|
SearchSuccessResponse
|
||||||
} from '../../core/cache/response-cache.models';
|
} from '../../core/cache/response.models';
|
||||||
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
|
|
||||||
import { ResponseCacheService } from '../../core/cache/response-cache.service';
|
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
import { ResponseParsingService } from '../../core/data/parsing.service';
|
import { ResponseParsingService } from '../../core/data/parsing.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
@@ -24,7 +22,11 @@ import { RequestService } from '../../core/data/request.service';
|
|||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { configureRequest, getSucceededRemoteData } from '../../core/shared/operators';
|
import {
|
||||||
|
configureRequest,
|
||||||
|
getResponseFromEntry,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../../core/shared/operators';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
||||||
@@ -68,7 +70,6 @@ export class SearchService implements OnDestroy {
|
|||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
protected responseCache: ResponseCacheService,
|
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
private rdb: RemoteDataBuildService,
|
private rdb: RemoteDataBuildService,
|
||||||
private halService: HALEndpointService,
|
private halService: HALEndpointService,
|
||||||
@@ -98,16 +99,12 @@ export class SearchService implements OnDestroy {
|
|||||||
configureRequest(this.requestService)
|
configureRequest(this.requestService)
|
||||||
);
|
);
|
||||||
const requestEntryObs = requestObs.pipe(
|
const requestEntryObs = requestObs.pipe(
|
||||||
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
switchMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
||||||
);
|
|
||||||
|
|
||||||
const responseCacheObs = requestObs.pipe(
|
|
||||||
flatMap((request: RestRequest) => this.responseCache.get(request.href))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const sqrObs: Observable<SearchQueryResponse> = responseCacheObs.pipe(
|
const sqrObs: Observable<SearchQueryResponse> = requestEntryObs.pipe(
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
getResponseFromEntry(),
|
||||||
map((response: SearchSuccessResponse) => response.results)
|
map((response: SearchSuccessResponse) => response.results)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -115,39 +112,43 @@ export class SearchService implements OnDestroy {
|
|||||||
// Turn list of observable remote data DSO's into observable remote data object with list of DSO
|
// Turn list of observable remote data DSO's into observable remote data object with list of DSO
|
||||||
const dsoObs: Observable<RemoteData<DSpaceObject[]>> = sqrObs.pipe(
|
const dsoObs: Observable<RemoteData<DSpaceObject[]>> = sqrObs.pipe(
|
||||||
map((sqr: SearchQueryResponse) => {
|
map((sqr: SearchQueryResponse) => {
|
||||||
return sqr.objects.map((nsr: NormalizedSearchResult) =>
|
return sqr.objects.map((nsr: NormalizedSearchResult) => {
|
||||||
this.rdb.buildSingle(nsr.dspaceObject));
|
return this.rdb.buildSingle(nsr.dspaceObject);
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
flatMap((input: Array<Observable<RemoteData<DSpaceObject>>>) => this.rdb.aggregate(input))
|
switchMap((input: Array<Observable<RemoteData<DSpaceObject>>>) => this.rdb.aggregate(input)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create search results again with the correct dso objects linked to each result
|
// Create search results again with the correct dso objects linked to each result
|
||||||
const tDomainListObs = Observable.combineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData<DSpaceObject[]>) => {
|
const tDomainListObs = observableCombineLatest(sqrObs, dsoObs).pipe(
|
||||||
|
map(([sqr, dsos]) => {
|
||||||
|
return sqr.objects.map((object: NormalizedSearchResult, index: number) => {
|
||||||
|
let co = DSpaceObject;
|
||||||
|
if (dsos.payload[index]) {
|
||||||
|
const constructor: GenericConstructor<ListableObject> = dsos.payload[index].constructor as GenericConstructor<ListableObject>;
|
||||||
|
co = getSearchResultFor(constructor);
|
||||||
|
return Object.assign(new co(), object, {
|
||||||
|
dspaceObject: dsos.payload[index]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return sqr.objects.map((object: NormalizedSearchResult, index: number) => {
|
const pageInfoObs: Observable<PageInfo> = requestEntryObs.pipe(
|
||||||
let co = DSpaceObject;
|
getResponseFromEntry(),
|
||||||
if (dsos.payload[index]) {
|
|
||||||
const constructor: GenericConstructor<ListableObject> = dsos.payload[index].constructor as GenericConstructor<ListableObject>;
|
|
||||||
co = getSearchResultFor(constructor);
|
|
||||||
return Object.assign(new co(), object, {
|
|
||||||
dspaceObject: dsos.payload[index]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const pageInfoObs: Observable<PageInfo> = responseCacheObs.pipe(
|
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
|
||||||
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
||||||
);
|
);
|
||||||
|
|
||||||
const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => {
|
const payloadObs = observableCombineLatest(tDomainListObs, pageInfoObs).pipe(
|
||||||
return new PaginatedList(pageInfo, tDomainList);
|
map(([tDomainList, pageInfo]) => {
|
||||||
});
|
return new PaginatedList(pageInfo, tDomainList);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,21 +180,17 @@ export class SearchService implements OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const requestEntryObs = requestObs.pipe(
|
const requestEntryObs = requestObs.pipe(
|
||||||
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
switchMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
||||||
);
|
|
||||||
|
|
||||||
const responseCacheObs = requestObs.pipe(
|
|
||||||
flatMap((request: RestRequest) => this.responseCache.get(request.href))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const facetConfigObs: Observable<SearchFilterConfig[]> = responseCacheObs.pipe(
|
const facetConfigObs: Observable<SearchFilterConfig[]> = requestEntryObs.pipe(
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
getResponseFromEntry(),
|
||||||
map((response: FacetConfigSuccessResponse) =>
|
map((response: FacetConfigSuccessResponse) =>
|
||||||
response.results.map((result: any) => Object.assign(new SearchFilterConfig(), result)))
|
response.results.map((result: any) => Object.assign(new SearchFilterConfig(), result)))
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, facetConfigObs);
|
return this.rdb.toRemoteDataObservable(requestEntryObs, facetConfigObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,29 +223,27 @@ export class SearchService implements OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const requestEntryObs = requestObs.pipe(
|
const requestEntryObs = requestObs.pipe(
|
||||||
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
switchMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
||||||
);
|
|
||||||
|
|
||||||
const responseCacheObs = requestObs.pipe(
|
|
||||||
flatMap((request: RestRequest) => this.responseCache.get(request.href))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const facetValueObs: Observable<FacetValue[]> = responseCacheObs.pipe(
|
const facetValueObs: Observable<FacetValue[]> = requestEntryObs.pipe(
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
getResponseFromEntry(),
|
||||||
map((response: FacetValueSuccessResponse) => response.results)
|
map((response: FacetValueSuccessResponse) => response.results)
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageInfoObs: Observable<PageInfo> = responseCacheObs.pipe(
|
const pageInfoObs: Observable<PageInfo> = requestEntryObs.pipe(
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
getResponseFromEntry(),
|
||||||
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
||||||
);
|
);
|
||||||
|
|
||||||
const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => {
|
const payloadObs = observableCombineLatest(facetValueObs, pageInfoObs).pipe(
|
||||||
return new PaginatedList(pageInfo, facetValue);
|
map(([facetValue, pageInfo]) => {
|
||||||
});
|
return new PaginatedList(pageInfo, facetValue);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,12 +267,14 @@ export class SearchService implements OnDestroy {
|
|||||||
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
|
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
|
||||||
if (dsoRD.payload.type === ResourceType.Community) {
|
if (dsoRD.payload.type === ResourceType.Community) {
|
||||||
const community: Community = dsoRD.payload as Community;
|
const community: Community = dsoRD.payload as Community;
|
||||||
return Observable.combineLatest(community.subcommunities, community.collections, (subCommunities, collections) => {
|
return observableCombineLatest(community.subcommunities, community.collections).pipe(
|
||||||
/*if this is a community, we also need to show the direct children*/
|
map(([subCommunities, collections]) => {
|
||||||
return [community, ...subCommunities.payload.page, ...collections.payload.page]
|
/*if this is a community, we also need to show the direct children*/
|
||||||
})
|
return [community, ...subCommunities.payload.page, ...collections.payload.page]
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Observable.of([dsoRD.payload]);
|
return observableOf([dsoRD.payload]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -291,13 +288,13 @@ export class SearchService implements OnDestroy {
|
|||||||
* @returns {Observable<ViewMode>} The current view mode
|
* @returns {Observable<ViewMode>} The current view mode
|
||||||
*/
|
*/
|
||||||
getViewMode(): Observable<ViewMode> {
|
getViewMode(): Observable<ViewMode> {
|
||||||
return this.route.queryParams.map((params) => {
|
return this.route.queryParams.pipe(map((params) => {
|
||||||
if (isNotEmpty(params.view) && hasValue(params.view)) {
|
if (isNotEmpty(params.view) && hasValue(params.view)) {
|
||||||
return params.view;
|
return params.view;
|
||||||
} else {
|
} else {
|
||||||
return ViewMode.List;
|
return ViewMode.List;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { SearchSettingsComponent } from './search-settings.component';
|
import { SearchSettingsComponent } from './search-settings.component';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -15,6 +15,7 @@ import { SearchFilterService } from '../search-filters/search-filter/search-filt
|
|||||||
import { hot } from 'jasmine-marbles';
|
import { hot } from 'jasmine-marbles';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
describe('SearchSettingsComponent', () => {
|
describe('SearchSettingsComponent', () => {
|
||||||
|
|
||||||
@@ -43,16 +44,16 @@ describe('SearchSettingsComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
queryParams: Observable.of({
|
queryParams: observableOf({
|
||||||
query: queryParam,
|
query: queryParam,
|
||||||
scope: scopeParam
|
scope: scopeParam
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarService = {
|
const sidebarService = {
|
||||||
isCollapsed: Observable.of(true),
|
isCollapsed: observableOf(true),
|
||||||
collapse: () => this.isCollapsed = Observable.of(true),
|
collapse: () => this.isCollapsed = observableOf(true),
|
||||||
expand: () => this.isCollapsed = Observable.of(false)
|
expand: () => this.isCollapsed = observableOf(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@@ -101,7 +102,7 @@ describe('SearchSettingsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('it should show the order settings with the respective selectable options', () => {
|
it('it should show the order settings with the respective selectable options', () => {
|
||||||
(comp as any).searchOptions$.first().subscribe((options) => {
|
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||||
expect(orderSetting).toBeDefined();
|
expect(orderSetting).toBeDefined();
|
||||||
@@ -111,7 +112,7 @@ describe('SearchSettingsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('it should show the size settings with the respective selectable options', () => {
|
it('it should show the size settings with the respective selectable options', () => {
|
||||||
(comp as any).searchOptions$.first().subscribe((options) => {
|
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
||||||
expect(pageSizeSetting).toBeDefined();
|
expect(pageSizeSetting).toBeDefined();
|
||||||
@@ -122,7 +123,7 @@ describe('SearchSettingsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have the proper order value selected by default', () => {
|
it('should have the proper order value selected by default', () => {
|
||||||
(comp as any).searchOptions$.first().subscribe((options) => {
|
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||||
const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]'));
|
const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]'));
|
||||||
@@ -131,7 +132,7 @@ describe('SearchSettingsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have the proper rpp value selected by default', () => {
|
it('should have the proper rpp value selected by default', () => {
|
||||||
(comp as any).searchOptions$.first().subscribe((options) => {
|
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
||||||
const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]'));
|
const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]'));
|
||||||
|
@@ -3,8 +3,7 @@ import { SearchService } from '../search-service/search.service';
|
|||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
import { Observable } from 'rxjs';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { provideMockActions } from '@ngrx/effects/testing';
|
import { provideMockActions } from '@ngrx/effects/testing';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
|
import { map, tap, filter } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Effect, Actions } from '@ngrx/effects'
|
import { Effect, Actions, ofType } from '@ngrx/effects'
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
||||||
@@ -12,10 +13,14 @@ import { URLBaser } from '../../core/url-baser/url-baser';
|
|||||||
export class SearchSidebarEffects {
|
export class SearchSidebarEffects {
|
||||||
private previousPath: string;
|
private previousPath: string;
|
||||||
@Effect() routeChange$ = this.actions$
|
@Effect() routeChange$ = this.actions$
|
||||||
.ofType(fromRouter.ROUTER_NAVIGATION)
|
.pipe(
|
||||||
.filter((action) => this.previousPath !== this.getBaseUrl(action))
|
ofType(fromRouter.ROUTER_NAVIGATION),
|
||||||
.do((action) => {this.previousPath = this.getBaseUrl(action)})
|
filter((action) => this.previousPath !== this.getBaseUrl(action)),
|
||||||
.map(() => new SearchSidebarCollapseAction());
|
tap((action) => {
|
||||||
|
this.previousPath = this.getBaseUrl(action)
|
||||||
|
}),
|
||||||
|
map(() => new SearchSidebarCollapseAction())
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private actions$: Actions) {
|
constructor(private actions$: Actions) {
|
||||||
|
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { SearchSidebarService } from './search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar.service';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { async, inject, TestBed } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
|
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
|
||||||
@@ -13,13 +12,13 @@ describe('SearchSidebarService', () => {
|
|||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
pipe: observableOf(true)
|
||||||
});
|
});
|
||||||
const windowService = jasmine.createSpyObj('hostWindowService',
|
const windowService = jasmine.createSpyObj('hostWindowService',
|
||||||
{
|
{
|
||||||
isXs: Observable.of(true),
|
isXs: observableOf(true),
|
||||||
isSm: Observable.of(false),
|
isSm: observableOf(false),
|
||||||
isXsOrSm: Observable.of(true)
|
isXsOrSm: observableOf(true)
|
||||||
});
|
});
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SearchSidebarState } from './search-sidebar.reducer';
|
import { SearchSidebarState } from './search-sidebar.reducer';
|
||||||
import { createSelector, Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
|
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
const sidebarStateSelector = (state: AppState) => state.searchSidebar;
|
const sidebarStateSelector = (state: AppState) => state.searchSidebar;
|
||||||
const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SearchSidebarState) => sidebar.sidebarCollapsed);
|
const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SearchSidebarState) => sidebar.sidebarCollapsed);
|
||||||
@@ -26,7 +27,7 @@ export class SearchSidebarService {
|
|||||||
|
|
||||||
constructor(private store: Store<AppState>, private windowService: HostWindowService) {
|
constructor(private store: Store<AppState>, private windowService: HostWindowService) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
this.isCollapsedInStore = this.store.select(sidebarCollapsedSelector);
|
this.isCollapsedInStore = this.store.pipe(select(sidebarCollapsedSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,10 +35,12 @@ export class SearchSidebarService {
|
|||||||
* @returns {Observable<boolean>} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed
|
* @returns {Observable<boolean>} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed
|
||||||
*/
|
*/
|
||||||
get isCollapsed(): Observable<boolean> {
|
get isCollapsed(): Observable<boolean> {
|
||||||
return Observable.combineLatest(
|
return observableCombineLatest(
|
||||||
this.isXsOrSm$,
|
this.isXsOrSm$,
|
||||||
this.isCollapsedInStore,
|
this.isCollapsedInStore
|
||||||
(mobile, store) => mobile ? store : true);
|
).pipe(
|
||||||
|
map(([mobile, store]) => mobile ? store : true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -12,6 +12,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
|||||||
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
||||||
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
||||||
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
||||||
|
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
||||||
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
|
||||||
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
||||||
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
||||||
|
@@ -92,7 +92,7 @@ describe('App component', () => {
|
|||||||
let store: Store<HostWindowState>;
|
let store: Store<HostWindowState>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store = fixture.debugElement.injector.get(Store);
|
store = fixture.debugElement.injector.get(Store) as Store<HostWindowState>;
|
||||||
spyOn(store, 'dispatch');
|
spyOn(store, 'dispatch');
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { filter, first, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@@ -9,7 +10,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -62,10 +63,10 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
||||||
|
|
||||||
// Whether is not authenticathed try to retrieve a possible stored auth token
|
// Whether is not authenticathed try to retrieve a possible stored auth token
|
||||||
this.store.select(isAuthenticated)
|
this.store.pipe(select(isAuthenticated),
|
||||||
.take(1)
|
first(),
|
||||||
.filter((authenticated) => !authenticated)
|
filter((authenticated) => !authenticated)
|
||||||
.subscribe((authenticated) => this.authService.checkAuthenticationToken());
|
).subscribe((authenticated) => this.authService.checkAuthenticationToken());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||||
import { META_REDUCERS, MetaReducer, StoreModule } from '@ngrx/store';
|
import { META_REDUCERS, MetaReducer, StoreModule } from '@ngrx/store';
|
||||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
|
||||||
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -47,10 +46,6 @@ export function getMetaReducers(config: GlobalConfig): Array<MetaReducer<AppStat
|
|||||||
|
|
||||||
const DEV_MODULES: any[] = [];
|
const DEV_MODULES: any[] = [];
|
||||||
|
|
||||||
if (!ENV_CONFIG.production) {
|
|
||||||
DEV_MODULES.push(StoreDevtoolsModule.instrument({ maxAge: 500 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
import { AuthType } from './auth-type';
|
import { AuthType } from './auth-type';
|
||||||
import { GenericConstructor } from '../shared/generic-constructor';
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
|
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
|
||||||
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model';
|
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
|
||||||
import { NormalizedEpersonModel } from '../eperson/models/NormalizedEperson.model';
|
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||||
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
|
|
||||||
export class AuthObjectFactory {
|
export class AuthObjectFactory {
|
||||||
public static getConstructor(type): GenericConstructor<NormalizedDSpaceObject> {
|
public static getConstructor(type): GenericConstructor<NormalizedObject> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AuthType.Eperson: {
|
case AuthType.EPerson: {
|
||||||
return NormalizedEpersonModel
|
return NormalizedEPerson
|
||||||
}
|
}
|
||||||
|
|
||||||
case AuthType.Status: {
|
case AuthType.Status: {
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
|
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
|
||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { GLOBAL_CONFIG } from '../../../config';
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models';
|
import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models';
|
||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
import { AuthStatusResponse, ErrorResponse } from '../cache/response.models';
|
||||||
import { AuthStatusResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
|
import { getResponseFromEntry } from '../shared/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthRequestService {
|
export class AuthRequestService {
|
||||||
@@ -18,23 +19,22 @@ export class AuthRequestService {
|
|||||||
|
|
||||||
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected responseCache: ResponseCacheService,
|
|
||||||
protected requestService: RequestService) {
|
protected requestService: RequestService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fetchRequest(request: RestRequest): Observable<any> {
|
protected fetchRequest(request: RestRequest): Observable<any> {
|
||||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
return this.requestService.getByUUID(request.uuid).pipe(
|
||||||
.map((entry: ResponseCacheEntry) => entry.response)
|
getResponseFromEntry(),
|
||||||
// TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
|
// TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
|
||||||
.do(() => this.responseCache.remove(request.href))
|
// tap(() => this.responseCache.remove(request.href)),
|
||||||
.partition((response: RestResponse) => response.isSuccessful);
|
mergeMap((response) => {
|
||||||
return Observable.merge(
|
if (response.isSuccessful && isNotEmpty(response)) {
|
||||||
errorResponse.flatMap((response: ErrorResponse) =>
|
return observableOf((response as AuthStatusResponse).response);
|
||||||
Observable.throw(new Error(response.errorMessage))),
|
} else if (!response.isSuccessful) {
|
||||||
successResponse
|
return observableThrowError(new Error((response as ErrorResponse).errorMessage));
|
||||||
.filter((response: AuthStatusResponse) => isNotEmpty(response))
|
}
|
||||||
.map((response: AuthStatusResponse) => response.response)
|
})
|
||||||
.distinctUntilChanged());
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getEndpointByMethod(endpoint: string, method: string): string {
|
protected getEndpointByMethod(endpoint: string, method: string): string {
|
||||||
@@ -42,24 +42,24 @@ export class AuthRequestService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
|
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
|
||||||
return this.halService.getEndpoint(this.linkName)
|
return this.halService.getEndpoint(this.linkName).pipe(
|
||||||
.filter((href: string) => isNotEmpty(href))
|
filter((href: string) => isNotEmpty(href)),
|
||||||
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method))
|
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
||||||
.distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
.map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options))
|
map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
|
||||||
.do((request: PostRequest) => this.requestService.configure(request, true))
|
tap((request: PostRequest) => this.requestService.configure(request, true)),
|
||||||
.flatMap((request: PostRequest) => this.fetchRequest(request))
|
mergeMap((request: PostRequest) => this.fetchRequest(request)),
|
||||||
.distinctUntilChanged();
|
distinctUntilChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRequest(method: string, options?: HttpOptions): Observable<any> {
|
public getRequest(method: string, options?: HttpOptions): Observable<any> {
|
||||||
return this.halService.getEndpoint(this.linkName)
|
return this.halService.getEndpoint(this.linkName).pipe(
|
||||||
.filter((href: string) => isNotEmpty(href))
|
filter((href: string) => isNotEmpty(href)),
|
||||||
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method))
|
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
||||||
.distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
.map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options))
|
map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)),
|
||||||
.do((request: PostRequest) => this.requestService.configure(request, true))
|
tap((request: PostRequest) => this.requestService.configure(request, true)),
|
||||||
.flatMap((request: PostRequest) => this.fetchRequest(request))
|
mergeMap((request: PostRequest) => this.fetchRequest(request)),
|
||||||
.distinctUntilChanged();
|
distinctUntilChanged());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,18 @@
|
|||||||
import { AuthStatusResponse } from '../cache/response-cache.models';
|
import { AuthStatusResponse } from '../cache/response.models';
|
||||||
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthResponseParsingService } from './auth-response-parsing.service';
|
import { AuthResponseParsingService } from './auth-response-parsing.service';
|
||||||
import { AuthGetRequest, AuthPostRequest } from '../data/request.models';
|
import { AuthGetRequest, AuthPostRequest } from '../data/request.models';
|
||||||
|
import { MockStore } from '../../shared/testing/mock-store';
|
||||||
|
import { ObjectCacheState } from '../cache/object-cache.reducer';
|
||||||
|
|
||||||
describe('ConfigResponseParsingService', () => {
|
describe('AuthResponseParsingService', () => {
|
||||||
let service: AuthResponseParsingService;
|
let service: AuthResponseParsingService;
|
||||||
|
|
||||||
const EnvConfig = {} as GlobalConfig;
|
const EnvConfig = { cache: { msToLive: 1000 } } as any;
|
||||||
const store = {} as Store<CoreState>;
|
const store = new MockStore<ObjectCacheState>({});
|
||||||
const objectCacheService = new ObjectCacheService(store);
|
const objectCacheService = new ObjectCacheService(store as any);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service = new AuthResponseParsingService(EnvConfig, objectCacheService);
|
service = new AuthResponseParsingService(EnvConfig, objectCacheService);
|
||||||
@@ -86,13 +84,19 @@ describe('ConfigResponseParsingService', () => {
|
|||||||
type: 'eperson',
|
type: 'eperson',
|
||||||
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
|
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
|
||||||
_links: {
|
_links: {
|
||||||
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
|
self: {
|
||||||
|
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
eperson: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b',
|
eperson: {
|
||||||
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
|
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
statusCode: '200'
|
statusCode: '200'
|
||||||
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core';
|
|||||||
|
|
||||||
import { AuthObjectFactory } from './auth-object-factory';
|
import { AuthObjectFactory } from './auth-object-factory';
|
||||||
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
|
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
|
||||||
import { AuthStatusResponse, RestResponse } from '../cache/response-cache.models';
|
import { AuthStatusResponse, RestResponse } from '../cache/response.models';
|
||||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
import { GLOBAL_CONFIG } from '../../../config';
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
@@ -12,22 +12,22 @@ import { ResponseParsingService } from '../data/parsing.service';
|
|||||||
import { RestRequest } from '../data/request.models';
|
import { RestRequest } from '../data/request.models';
|
||||||
import { AuthType } from './auth-type';
|
import { AuthType } from './auth-type';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
|
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
|
||||||
protected objectFactory = AuthObjectFactory;
|
protected objectFactory = AuthObjectFactory;
|
||||||
protected toCache = false;
|
protected toCache = true;
|
||||||
|
|
||||||
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
protected objectCache: ObjectCacheService,) {
|
protected objectCache: ObjectCacheService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) {
|
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) {
|
||||||
const response: AuthStatus = this.process<AuthStatus, AuthType>(data.payload, request.href);
|
const response = this.process<NormalizedAuthStatus, AuthType>(data.payload, request.href);
|
||||||
response.eperson = data.payload._embedded.eperson;
|
|
||||||
return new AuthStatusResponse(response, data.statusCode);
|
return new AuthStatusResponse(response, data.statusCode);
|
||||||
} else {
|
} else {
|
||||||
return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode);
|
return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export enum AuthType {
|
export enum AuthType {
|
||||||
Eperson = 'eperson',
|
EPerson = 'eperson',
|
||||||
Status = 'status'
|
Status = 'status'
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { Action } from '@ngrx/store';
|
|||||||
import { type } from '../../shared/ngrx/type';
|
import { type } from '../../shared/ngrx/type';
|
||||||
|
|
||||||
// import models
|
// import models
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
|
|
||||||
export const AuthActionTypes = {
|
export const AuthActionTypes = {
|
||||||
@@ -76,10 +76,10 @@ export class AuthenticatedSuccessAction implements Action {
|
|||||||
payload: {
|
payload: {
|
||||||
authenticated: boolean;
|
authenticated: boolean;
|
||||||
authToken: AuthTokenInfo;
|
authToken: AuthTokenInfo;
|
||||||
user: Eperson
|
user: EPerson
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(authenticated: boolean, authToken: AuthTokenInfo, user: Eperson) {
|
constructor(authenticated: boolean, authToken: AuthTokenInfo, user: EPerson) {
|
||||||
this.payload = { authenticated, authToken, user };
|
this.payload = { authenticated, authToken, user };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,9 +250,9 @@ export class RefreshTokenErrorAction implements Action {
|
|||||||
*/
|
*/
|
||||||
export class RegistrationAction implements Action {
|
export class RegistrationAction implements Action {
|
||||||
public type: string = AuthActionTypes.REGISTRATION;
|
public type: string = AuthActionTypes.REGISTRATION;
|
||||||
payload: Eperson;
|
payload: EPerson;
|
||||||
|
|
||||||
constructor(user: Eperson) {
|
constructor(user: EPerson) {
|
||||||
this.payload = user;
|
this.payload = user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,9 +278,9 @@ export class RegistrationErrorAction implements Action {
|
|||||||
*/
|
*/
|
||||||
export class RegistrationSuccessAction implements Action {
|
export class RegistrationSuccessAction implements Action {
|
||||||
public type: string = AuthActionTypes.REGISTRATION_SUCCESS;
|
public type: string = AuthActionTypes.REGISTRATION_SUCCESS;
|
||||||
payload: Eperson;
|
payload: EPerson;
|
||||||
|
|
||||||
constructor(user: Eperson) {
|
constructor(user: EPerson) {
|
||||||
this.payload = user;
|
this.payload = user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,7 @@ import { provideMockActions } from '@ngrx/effects/testing';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable, of as observableOf, throwError as observableThrow } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of'
|
|
||||||
|
|
||||||
import { AuthEffects } from './auth.effects';
|
import { AuthEffects } from './auth.effects';
|
||||||
import {
|
import {
|
||||||
@@ -25,22 +24,26 @@ import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
|||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
|
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
|
||||||
|
|
||||||
import { EpersonMock } from '../../shared/testing/eperson-mock';
|
import { EPersonMock } from '../../shared/testing/eperson-mock';
|
||||||
|
|
||||||
describe('AuthEffects', () => {
|
describe('AuthEffects', () => {
|
||||||
let authEffects: AuthEffects;
|
let authEffects: AuthEffects;
|
||||||
let actions: Observable<any>;
|
let actions: Observable<any>;
|
||||||
|
let authServiceStub;
|
||||||
const authServiceStub = new AuthServiceStub();
|
|
||||||
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
|
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
select: observableOf(true)
|
||||||
});
|
});
|
||||||
const token = authServiceStub.getToken();
|
let token;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
authServiceStub = new AuthServiceStub();
|
||||||
|
token = authServiceStub.getToken();
|
||||||
|
}
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
AuthEffects,
|
AuthEffects,
|
||||||
@@ -72,7 +75,7 @@ describe('AuthEffects', () => {
|
|||||||
|
|
||||||
describe('when credentials are wrong', () => {
|
describe('when credentials are wrong', () => {
|
||||||
it('should return a AUTHENTICATE_ERROR action in response to a AUTHENTICATE action', () => {
|
it('should return a AUTHENTICATE_ERROR action in response to a AUTHENTICATE action', () => {
|
||||||
spyOn((authEffects as any).authService, 'authenticate').and.returnValue(Observable.throw(new Error('Message Error test')));
|
spyOn((authEffects as any).authService, 'authenticate').and.returnValue(observableThrow(new Error('Message Error test')));
|
||||||
|
|
||||||
actions = hot('--a-', {
|
actions = hot('--a-', {
|
||||||
a: {
|
a: {
|
||||||
@@ -105,7 +108,7 @@ describe('AuthEffects', () => {
|
|||||||
it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => {
|
it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => {
|
||||||
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
|
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
|
||||||
|
|
||||||
const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EpersonMock)});
|
const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EPersonMock)});
|
||||||
|
|
||||||
expect(authEffects.authenticated$).toBeObservable(expected);
|
expect(authEffects.authenticated$).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
@@ -113,7 +116,7 @@ describe('AuthEffects', () => {
|
|||||||
|
|
||||||
describe('when token is not valid', () => {
|
describe('when token is not valid', () => {
|
||||||
it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => {
|
it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => {
|
||||||
spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(Observable.throw(new Error('Message Error test')));
|
spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test')));
|
||||||
|
|
||||||
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
|
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
|
||||||
|
|
||||||
@@ -139,7 +142,7 @@ describe('AuthEffects', () => {
|
|||||||
|
|
||||||
describe('when check token failed', () => {
|
describe('when check token failed', () => {
|
||||||
it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => {
|
it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => {
|
||||||
spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(Observable.throw(''));
|
spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(observableThrow(''));
|
||||||
|
|
||||||
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
|
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
|
||||||
|
|
||||||
@@ -165,7 +168,7 @@ describe('AuthEffects', () => {
|
|||||||
|
|
||||||
describe('when refresh token failed', () => {
|
describe('when refresh token failed', () => {
|
||||||
it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => {
|
it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => {
|
||||||
spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(Observable.throw(''));
|
spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(observableThrow(''));
|
||||||
|
|
||||||
actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}});
|
actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}});
|
||||||
|
|
||||||
@@ -191,7 +194,7 @@ describe('AuthEffects', () => {
|
|||||||
|
|
||||||
describe('when refresh token failed', () => {
|
describe('when refresh token failed', () => {
|
||||||
it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => {
|
it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => {
|
||||||
spyOn((authEffects as any).authService, 'logout').and.returnValue(Observable.throw(new Error('Message Error test')));
|
spyOn((authEffects as any).authService, 'logout').and.returnValue(observableThrow(new Error('Message Error test')));
|
||||||
|
|
||||||
actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT, payload: token}});
|
actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT, payload: token}});
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
|
import { of as observableOf, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { filter, debounceTime, switchMap, take, tap, catchError, map, first } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
// import @ngrx
|
// import @ngrx
|
||||||
import { Actions, Effect } from '@ngrx/effects';
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, select, Store } from '@ngrx/store';
|
||||||
|
|
||||||
// import rxjs
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
|
|
||||||
// import services
|
// import services
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
RegistrationErrorAction,
|
RegistrationErrorAction,
|
||||||
RegistrationSuccessAction
|
RegistrationSuccessAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
@@ -43,112 +43,131 @@ export class AuthEffects {
|
|||||||
* @method authenticate
|
* @method authenticate
|
||||||
*/
|
*/
|
||||||
@Effect()
|
@Effect()
|
||||||
public authenticate$: Observable<Action> = this.actions$
|
public authenticate$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.AUTHENTICATE)
|
ofType(AuthActionTypes.AUTHENTICATE),
|
||||||
.switchMap((action: AuthenticateAction) => {
|
switchMap((action: AuthenticateAction) => {
|
||||||
return this.authService.authenticate(action.payload.email, action.payload.password)
|
return this.authService.authenticate(action.payload.email, action.payload.password).pipe(
|
||||||
.first()
|
first(),
|
||||||
.map((response: AuthStatus) => new AuthenticationSuccessAction(response.token))
|
map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
|
||||||
.catch((error) => Observable.of(new AuthenticationErrorAction(error)));
|
catchError((error) => observableOf(new AuthenticationErrorAction(error)))
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public authenticateSuccess$: Observable<Action> = this.actions$
|
public authenticateSuccess$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.AUTHENTICATE_SUCCESS)
|
ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
|
||||||
.do((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload))
|
tap((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload)),
|
||||||
.map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload));
|
map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
|
||||||
|
);
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public authenticated$: Observable<Action> = this.actions$
|
public authenticated$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.AUTHENTICATED)
|
ofType(AuthActionTypes.AUTHENTICATED),
|
||||||
.switchMap((action: AuthenticatedAction) => {
|
switchMap((action: AuthenticatedAction) => {
|
||||||
return this.authService.authenticatedUser(action.payload)
|
return this.authService.authenticatedUser(action.payload).pipe(
|
||||||
.map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user))
|
map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
|
||||||
.catch((error) => Observable.of(new AuthenticatedErrorAction(error)));
|
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// It means "reacts to this action but don't send another"
|
// It means "reacts to this action but don't send another"
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public authenticatedError$: Observable<Action> = this.actions$
|
public authenticatedError$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.AUTHENTICATED_ERROR)
|
ofType(AuthActionTypes.AUTHENTICATED_ERROR),
|
||||||
.do((action: LogOutSuccessAction) => this.authService.removeToken());
|
tap((action: LogOutSuccessAction) => this.authService.removeToken())
|
||||||
|
);
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public checkToken$: Observable<Action> = this.actions$
|
public checkToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN),
|
||||||
.ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN)
|
switchMap(() => {
|
||||||
.switchMap(() => {
|
return this.authService.hasValidAuthenticationToken().pipe(
|
||||||
return this.authService.hasValidAuthenticationToken()
|
map((token: AuthTokenInfo) => new AuthenticatedAction(token)),
|
||||||
.map((token: AuthTokenInfo) => new AuthenticatedAction(token))
|
catchError((error) => observableOf(new CheckAuthenticationTokenErrorAction()))
|
||||||
.catch((error) => Observable.of(new CheckAuthenticationTokenErrorAction()));
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public createUser$: Observable<Action> = this.actions$
|
public createUser$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.REGISTRATION)
|
ofType(AuthActionTypes.REGISTRATION),
|
||||||
.debounceTime(500) // to remove when functionality is implemented
|
debounceTime(500), // to remove when functionality is implemented
|
||||||
.switchMap((action: RegistrationAction) => {
|
switchMap((action: RegistrationAction) => {
|
||||||
return this.authService.create(action.payload)
|
return this.authService.create(action.payload).pipe(
|
||||||
.map((user: Eperson) => new RegistrationSuccessAction(user))
|
map((user: EPerson) => new RegistrationSuccessAction(user)),
|
||||||
.catch((error) => Observable.of(new RegistrationErrorAction(error)));
|
catchError((error) => observableOf(new RegistrationErrorAction(error)))
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public refreshToken$: Observable<Action> = this.actions$
|
public refreshToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.REFRESH_TOKEN),
|
||||||
.ofType(AuthActionTypes.REFRESH_TOKEN)
|
switchMap((action: RefreshTokenAction) => {
|
||||||
.switchMap((action: RefreshTokenAction) => {
|
return this.authService.refreshAuthenticationToken(action.payload).pipe(
|
||||||
return this.authService.refreshAuthenticationToken(action.payload)
|
map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token)),
|
||||||
.map((token: AuthTokenInfo) => new RefreshTokenSuccessAction(token))
|
catchError((error) => observableOf(new RefreshTokenErrorAction()))
|
||||||
.catch((error) => Observable.of(new RefreshTokenErrorAction()));
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// It means "reacts to this action but don't send another"
|
// It means "reacts to this action but don't send another"
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public refreshTokenSuccess$: Observable<Action> = this.actions$
|
public refreshTokenSuccess$: Observable<Action> = this.actions$.pipe(
|
||||||
.ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS)
|
ofType(AuthActionTypes.REFRESH_TOKEN_SUCCESS),
|
||||||
.do((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload));
|
tap((action: RefreshTokenSuccessAction) => this.authService.replaceToken(action.payload))
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the store is rehydrated in the browser,
|
* When the store is rehydrated in the browser,
|
||||||
* clear a possible invalid token or authentication errors
|
* clear a possible invalid token or authentication errors
|
||||||
*/
|
*/
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$
|
public clearInvalidTokenOnRehydrate$: Observable<any> = this.actions$.pipe(
|
||||||
.ofType(StoreActionTypes.REHYDRATE)
|
ofType(StoreActionTypes.REHYDRATE),
|
||||||
.switchMap(() => {
|
switchMap(() => {
|
||||||
return this.store.select(isAuthenticated)
|
return this.store.pipe(
|
||||||
.take(1)
|
select(isAuthenticated),
|
||||||
.filter((authenticated) => !authenticated)
|
first(),
|
||||||
.do(() => this.authService.removeToken())
|
filter((authenticated) => !authenticated),
|
||||||
.do(() => this.authService.resetAuthenticationError());
|
tap(() => this.authService.removeToken()),
|
||||||
});
|
tap(() => this.authService.resetAuthenticationError())
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
public logOut$: Observable<Action> = this.actions$
|
public logOut$: Observable<Action> = this.actions$
|
||||||
.ofType(AuthActionTypes.LOG_OUT)
|
.pipe(
|
||||||
.switchMap(() => {
|
ofType(AuthActionTypes.LOG_OUT),
|
||||||
return this.authService.logout()
|
switchMap(() => {
|
||||||
.map((value) => new LogOutSuccessAction())
|
return this.authService.logout().pipe(
|
||||||
.catch((error) => Observable.of(new LogOutErrorAction(error)));
|
map((value) => new LogOutSuccessAction()),
|
||||||
});
|
catchError((error) => observableOf(new LogOutErrorAction(error)))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public logOutSuccess$: Observable<Action> = this.actions$
|
public logOutSuccess$: Observable<Action> = this.actions$
|
||||||
.ofType(AuthActionTypes.LOG_OUT_SUCCESS)
|
.pipe(ofType(AuthActionTypes.LOG_OUT_SUCCESS),
|
||||||
.do(() => this.authService.removeToken())
|
tap(() => this.authService.removeToken()),
|
||||||
.do(() => this.authService.clearRedirectUrl())
|
tap(() => this.authService.clearRedirectUrl()),
|
||||||
.do(() => this.authService.refreshAfterLogout());
|
tap(() => this.authService.refreshAfterLogout())
|
||||||
|
);
|
||||||
|
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public redirectToLogin$: Observable<Action> = this.actions$
|
public redirectToLogin$: Observable<Action> = this.actions$
|
||||||
.ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED)
|
.pipe(ofType(AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED),
|
||||||
.do(() => this.authService.removeToken())
|
tap(() => this.authService.removeToken()),
|
||||||
.do(() => this.authService.redirectToLogin());
|
tap(() => this.authService.redirectToLogin())
|
||||||
|
);
|
||||||
|
|
||||||
@Effect({dispatch: false})
|
@Effect({ dispatch: false })
|
||||||
public redirectToLoginTokenExpired$: Observable<Action> = this.actions$
|
public redirectToLoginTokenExpired$: Observable<Action> = this.actions$
|
||||||
.ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED)
|
.pipe(
|
||||||
.do(() => this.authService.removeToken())
|
ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED),
|
||||||
.do(() => this.authService.redirectToLoginWhenTokenExpired());
|
tap(() => this.authService.removeToken()),
|
||||||
|
tap(() => this.authService.redirectToLoginWhenTokenExpired())
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@@ -4,15 +4,15 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { AuthInterceptor } from './auth.interceptor';
|
import { AuthInterceptor } from './auth.interceptor';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { RestRequestMethod } from '../data/request.models';
|
|
||||||
import { RouterStub } from '../../shared/testing/router-stub';
|
import { RouterStub } from '../../shared/testing/router-stub';
|
||||||
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
|
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
|
||||||
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
||||||
|
import { RestRequestMethod } from '../data/rest-request-method';
|
||||||
|
|
||||||
describe(`AuthInterceptor`, () => {
|
describe(`AuthInterceptor`, () => {
|
||||||
let service: DSpaceRESTv2Service;
|
let service: DSpaceRESTv2Service;
|
||||||
@@ -23,7 +23,7 @@ describe(`AuthInterceptor`, () => {
|
|||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
select: Observable.of(true)
|
select: observableOf(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -49,7 +49,7 @@ describe(`AuthInterceptor`, () => {
|
|||||||
describe('when has a valid token', () => {
|
describe('when has a valid token', () => {
|
||||||
|
|
||||||
it('should not add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
it('should not add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
||||||
service.request(RestRequestMethod.Post, 'dspace-spring-rest/api/authn/login', 'password=password&user=user').subscribe((response) => {
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/authn/login', 'password=password&user=user').subscribe((response) => {
|
||||||
expect(response).toBeTruthy();
|
expect(response).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ describe(`AuthInterceptor`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
it('should add an Authorization header when we’re sending a HTTP request to \'authn\' endpoint', () => {
|
||||||
service.request(RestRequestMethod.Post, 'dspace-spring-rest/api/submission/workspaceitems', 'test').subscribe((response) => {
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/submission/workspaceitems', 'test').subscribe((response) => {
|
||||||
expect(response).toBeTruthy();
|
expect(response).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,11 +85,11 @@ describe(`AuthInterceptor`, () => {
|
|||||||
|
|
||||||
it('should redirect to login', () => {
|
it('should redirect to login', () => {
|
||||||
|
|
||||||
service.request(RestRequestMethod.Post, 'dspace-spring-rest/api/submission/workspaceitems', 'password=password&user=user').subscribe((response) => {
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/submission/workspaceitems', 'password=password&user=user').subscribe((response) => {
|
||||||
expect(response).toBeTruthy();
|
expect(response).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
service.request(RestRequestMethod.Post, 'dspace-spring-rest/api/submission/workspaceitems', 'password=password&user=user');
|
service.request(RestRequestMethod.POST, 'dspace-spring-rest/api/submission/workspaceitems', 'password=password&user=user');
|
||||||
|
|
||||||
httpMock.expectNone('dspace-spring-rest/api/submission/workspaceitems');
|
httpMock.expectNone('dspace-spring-rest/api/submission/workspaceitems');
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,16 @@
|
|||||||
|
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
|
||||||
|
|
||||||
|
import { catchError, filter, map } from 'rxjs/operators';
|
||||||
import { Injectable, Injector } from '@angular/core';
|
import { Injectable, Injector } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse,
|
HttpErrorResponse,
|
||||||
HttpErrorResponse, HttpResponseBase
|
HttpEvent,
|
||||||
|
HttpHandler,
|
||||||
|
HttpInterceptor,
|
||||||
|
HttpRequest,
|
||||||
|
HttpResponse,
|
||||||
|
HttpResponseBase
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import 'rxjs/add/observable/throw'
|
|
||||||
import 'rxjs/add/operator/catch';
|
|
||||||
|
|
||||||
import { find } from 'lodash';
|
import { find } from 'lodash';
|
||||||
|
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
@@ -35,7 +38,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isSuccess(response: HttpResponseBase): boolean {
|
private isSuccess(response: HttpResponseBase): boolean {
|
||||||
return response.status === 200;
|
return (response.status === 200 || response.status === 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean {
|
private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean {
|
||||||
@@ -79,11 +82,11 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
// The access token is expired
|
// The access token is expired
|
||||||
// Redirect to the login route
|
// Redirect to the login route
|
||||||
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
|
this.store.dispatch(new RedirectWhenTokenExpiredAction('auth.messages.expired'));
|
||||||
return Observable.of(null);
|
return observableOf(null);
|
||||||
} else if (!this.isAuthRequest(req) && isNotEmpty(token)) {
|
} else if (!this.isAuthRequest(req) && isNotEmpty(token)) {
|
||||||
// Intercept a request that is not to the authentication endpoint
|
// Intercept a request that is not to the authentication endpoint
|
||||||
authService.isTokenExpiring()
|
authService.isTokenExpiring().pipe(
|
||||||
.filter((isExpiring) => isExpiring)
|
filter((isExpiring) => isExpiring))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
// If the current request url is already in the refresh token request list, skip it
|
// If the current request url is already in the refresh token request list, skip it
|
||||||
if (isUndefined(find(this.refreshTokenRequestUrls, req.url))) {
|
if (isUndefined(find(this.refreshTokenRequestUrls, req.url))) {
|
||||||
@@ -101,8 +104,8 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pass on the new request instead of the original request.
|
// Pass on the new request instead of the original request.
|
||||||
return next.handle(newReq)
|
return next.handle(newReq).pipe(
|
||||||
.map((response) => {
|
map((response) => {
|
||||||
// Intercept a Login/Logout response
|
// Intercept a Login/Logout response
|
||||||
if (response instanceof HttpResponse && this.isSuccess(response) && (this.isLoginResponse(response) || this.isLogoutResponse(response))) {
|
if (response instanceof HttpResponse && this.isSuccess(response) && (this.isLoginResponse(response) || this.isLogoutResponse(response))) {
|
||||||
// It's a success Login/Logout response
|
// It's a success Login/Logout response
|
||||||
@@ -122,8 +125,8 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
} else {
|
} else {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
.catch((error, caught) => {
|
catchError((error, caught) => {
|
||||||
// Intercept an error response
|
// Intercept an error response
|
||||||
if (error instanceof HttpErrorResponse) {
|
if (error instanceof HttpErrorResponse) {
|
||||||
// Checks if is a response from a request to an authentication endpoint
|
// Checks if is a response from a request to an authentication endpoint
|
||||||
@@ -138,7 +141,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
statusText: error.statusText,
|
statusText: error.statusText,
|
||||||
url: error.url
|
url: error.url
|
||||||
});
|
});
|
||||||
return Observable.of(authResponse);
|
return observableOf(authResponse);
|
||||||
} else if (this.isUnauthorized(error)) {
|
} else if (this.isUnauthorized(error)) {
|
||||||
// The access token provided is expired, revoked, malformed, or invalid for other reasons
|
// The access token provided is expired, revoked, malformed, or invalid for other reasons
|
||||||
// Redirect to the login route
|
// Redirect to the login route
|
||||||
@@ -146,8 +149,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return error response as is.
|
// Return error response as is.
|
||||||
return Observable.throw(error);
|
return observableThrowError(error);
|
||||||
}) as any;
|
})) as any;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ import {
|
|||||||
SetRedirectUrlAction
|
SetRedirectUrlAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { EpersonMock } from '../../shared/testing/eperson-mock';
|
import { EPersonMock } from '../../shared/testing/eperson-mock';
|
||||||
|
|
||||||
describe('authReducer', () => {
|
describe('authReducer', () => {
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ describe('authReducer', () => {
|
|||||||
loading: true,
|
loading: true,
|
||||||
info: undefined
|
info: undefined
|
||||||
};
|
};
|
||||||
const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EpersonMock);
|
const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EPersonMock);
|
||||||
const newState = authReducer(initialState, action);
|
const newState = authReducer(initialState, action);
|
||||||
state = {
|
state = {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
@@ -116,7 +116,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
});
|
});
|
||||||
@@ -182,7 +182,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
|
|
||||||
const action = new LogOutAction();
|
const action = new LogOutAction();
|
||||||
@@ -199,7 +199,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
|
|
||||||
const action = new LogOutSuccessAction();
|
const action = new LogOutSuccessAction();
|
||||||
@@ -225,7 +225,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
|
|
||||||
const action = new LogOutErrorAction(mockError);
|
const action = new LogOutErrorAction(mockError);
|
||||||
@@ -237,7 +237,7 @@ describe('authReducer', () => {
|
|||||||
error: 'Test error message',
|
error: 'Test error message',
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
});
|
});
|
||||||
@@ -250,7 +250,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
const newTokenInfo = new AuthTokenInfo('Refreshed token');
|
const newTokenInfo = new AuthTokenInfo('Refreshed token');
|
||||||
const action = new RefreshTokenAction(newTokenInfo);
|
const action = new RefreshTokenAction(newTokenInfo);
|
||||||
@@ -262,7 +262,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock,
|
user: EPersonMock,
|
||||||
refreshing: true
|
refreshing: true
|
||||||
};
|
};
|
||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
@@ -276,7 +276,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock,
|
user: EPersonMock,
|
||||||
refreshing: true
|
refreshing: true
|
||||||
};
|
};
|
||||||
const newTokenInfo = new AuthTokenInfo('Refreshed token');
|
const newTokenInfo = new AuthTokenInfo('Refreshed token');
|
||||||
@@ -289,7 +289,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock,
|
user: EPersonMock,
|
||||||
refreshing: false
|
refreshing: false
|
||||||
};
|
};
|
||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
@@ -303,7 +303,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock,
|
user: EPersonMock,
|
||||||
refreshing: true
|
refreshing: true
|
||||||
};
|
};
|
||||||
const action = new RefreshTokenErrorAction();
|
const action = new RefreshTokenErrorAction();
|
||||||
@@ -329,7 +329,7 @@ describe('authReducer', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@@ -12,7 +12,7 @@ import {
|
|||||||
SetRedirectUrlAction
|
SetRedirectUrlAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
// import models
|
// import models
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +46,7 @@ export interface AuthState {
|
|||||||
refreshing?: boolean;
|
refreshing?: boolean;
|
||||||
|
|
||||||
// the authenticated user
|
// the authenticated user
|
||||||
user?: Eperson;
|
user?: EPerson;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,9 +3,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Store, StoreModule } from '@ngrx/store';
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
||||||
import 'rxjs/add/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { authReducer, AuthState } from './auth.reducer';
|
import { authReducer, AuthState } from './auth.reducer';
|
||||||
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
|
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
|
||||||
@@ -18,49 +17,64 @@ import { AuthRequestServiceStub } from '../../shared/testing/auth-request-servic
|
|||||||
import { AuthRequestService } from './auth-request.service';
|
import { AuthRequestService } from './auth-request.service';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { EpersonMock } from '../../shared/testing/eperson-mock';
|
import { EPersonMock } from '../../shared/testing/eperson-mock';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { ClientCookieService } from '../../shared/services/client-cookie.service';
|
import { ClientCookieService } from '../../shared/services/client-cookie.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
|
||||||
|
|
||||||
describe('AuthService test', () => {
|
describe('AuthService test', () => {
|
||||||
|
|
||||||
const mockStore: Store<AuthState> = jasmine.createSpyObj('store', {
|
const mockStore: Store<AuthState> = jasmine.createSpyObj('store', {
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
select: Observable.of(true)
|
pipe: observableOf(true)
|
||||||
});
|
});
|
||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
const authRequest = new AuthRequestServiceStub();
|
let authRequest;
|
||||||
const window = new NativeWindowRef();
|
const window = new NativeWindowRef();
|
||||||
const routerStub = new RouterStub();
|
const routerStub = new RouterStub();
|
||||||
const routeStub = new ActivatedRouteStub();
|
let routeStub;
|
||||||
let storage: CookieService;
|
let storage: CookieService;
|
||||||
const token: AuthTokenInfo = new AuthTokenInfo('test_token');
|
let token: AuthTokenInfo;
|
||||||
token.expires = Date.now() + (1000 * 60 * 60);
|
let authenticatedState;
|
||||||
let authenticatedState = {
|
const rdbService = getMockRemoteDataBuildService();
|
||||||
authenticated: true,
|
|
||||||
loaded: true,
|
function init() {
|
||||||
loading: false,
|
token = new AuthTokenInfo('test_token');
|
||||||
authToken: token,
|
token.expires = Date.now() + (1000 * 60 * 60);
|
||||||
user: EpersonMock
|
authenticatedState = {
|
||||||
};
|
authenticated: true,
|
||||||
|
loaded: true,
|
||||||
|
loading: false,
|
||||||
|
authToken: token,
|
||||||
|
user: EPersonMock
|
||||||
|
};
|
||||||
|
authRequest = new AuthRequestServiceStub();
|
||||||
|
routeStub = new ActivatedRouteStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
describe('', () => {
|
describe('', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
StoreModule.forRoot({authReducer}),
|
StoreModule.forRoot({ authReducer }),
|
||||||
],
|
],
|
||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AuthRequestService, useValue: authRequest},
|
{ provide: AuthRequestService, useValue: authRequest },
|
||||||
{provide: NativeWindowService, useValue: window},
|
{ provide: NativeWindowService, useValue: window },
|
||||||
{provide: REQUEST, useValue: {}},
|
{ provide: REQUEST, useValue: {} },
|
||||||
{provide: Router, useValue: routerStub},
|
{ provide: Router, useValue: routerStub },
|
||||||
{provide: ActivatedRoute, useValue: routeStub},
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
{provide: Store, useValue: mockStore},
|
{provide: Store, useValue: mockStore},
|
||||||
|
{ provide: RemoteDataBuildService, useValue: rdbService },
|
||||||
CookieService,
|
CookieService,
|
||||||
AuthService
|
AuthService
|
||||||
],
|
],
|
||||||
@@ -79,7 +93,7 @@ describe('AuthService test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return the authenticated user object when user token is valid', () => {
|
it('should return the authenticated user object when user token is valid', () => {
|
||||||
authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: Eperson) => {
|
authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: EPerson) => {
|
||||||
expect(user).toBeDefined();
|
expect(user).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -113,14 +127,16 @@ describe('AuthService test', () => {
|
|||||||
describe('', () => {
|
describe('', () => {
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
StoreModule.forRoot({authReducer})
|
StoreModule.forRoot({ authReducer })
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AuthRequestService, useValue: authRequest},
|
{ provide: AuthRequestService, useValue: authRequest },
|
||||||
{provide: REQUEST, useValue: {}},
|
{ provide: REQUEST, useValue: {} },
|
||||||
{provide: Router, useValue: routerStub},
|
{ provide: Router, useValue: routerStub },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: rdbService },
|
||||||
CookieService
|
CookieService
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -132,7 +148,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, authReqService, router, cookieService, store);
|
authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should return true when user is logged in', () => {
|
it('should return true when user is logged in', () => {
|
||||||
@@ -164,12 +180,12 @@ describe('AuthService test', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
StoreModule.forRoot({authReducer})
|
StoreModule.forRoot({ authReducer })
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AuthRequestService, useValue: authRequest},
|
{ provide: AuthRequestService, useValue: authRequest },
|
||||||
{provide: REQUEST, useValue: {}},
|
{ provide: REQUEST, useValue: {} },
|
||||||
{provide: Router, useValue: routerStub},
|
{ provide: Router, useValue: routerStub },
|
||||||
ClientCookieService,
|
ClientCookieService,
|
||||||
CookieService
|
CookieService
|
||||||
]
|
]
|
||||||
@@ -184,14 +200,14 @@ describe('AuthService test', () => {
|
|||||||
loaded: true,
|
loaded: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
authToken: expiredToken,
|
authToken: expiredToken,
|
||||||
user: EpersonMock
|
user: EPersonMock
|
||||||
};
|
};
|
||||||
store
|
store
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, authReqService, router, cookieService, store);
|
authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
|
||||||
storage = (authService as any).storage;
|
storage = (authService as any).storage;
|
||||||
spyOn(storage, 'get');
|
spyOn(storage, 'get');
|
||||||
spyOn(storage, 'remove');
|
spyOn(storage, 'remove');
|
||||||
|
@@ -1,15 +1,24 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import {
|
||||||
|
distinctUntilChanged,
|
||||||
|
filter,
|
||||||
|
first,
|
||||||
|
map,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
withLatestFrom
|
||||||
|
} from 'rxjs/operators';
|
||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { PRIMARY_OUTLET, Router, UrlSegmentGroup, UrlTree } from '@angular/router';
|
import { PRIMARY_OUTLET, Router, UrlSegmentGroup, UrlTree } from '@angular/router';
|
||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
||||||
|
|
||||||
import { RouterReducerState } from '@ngrx/router-store';
|
import { RouterReducerState } from '@ngrx/router-store';
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { CookieAttributes } from 'js-cookie';
|
import { CookieAttributes } from 'js-cookie';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { map, withLatestFrom } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthRequestService } from './auth-request.service';
|
import { AuthRequestService } from './auth-request.service';
|
||||||
|
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
@@ -17,11 +26,18 @@ import { AuthStatus } from './models/auth-status.model';
|
|||||||
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
|
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
|
||||||
import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
|
import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
|
||||||
import { CookieService } from '../../shared/services/cookie.service';
|
import { CookieService } from '../../shared/services/cookie.service';
|
||||||
import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
|
import {
|
||||||
|
getAuthenticationToken,
|
||||||
|
getRedirectUrl,
|
||||||
|
isAuthenticated,
|
||||||
|
isTokenRefreshing
|
||||||
|
} from './selectors';
|
||||||
import { AppState, routerStateSelector } from '../../app.reducer';
|
import { AppState, routerStateSelector } from '../../app.reducer';
|
||||||
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
|
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
|
||||||
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
|
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
|
||||||
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
|
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
|
||||||
|
|
||||||
export const LOGIN_ROUTE = '/login';
|
export const LOGIN_ROUTE = '/login';
|
||||||
export const LOGOUT_ROUTE = '/logout';
|
export const LOGOUT_ROUTE = '/logout';
|
||||||
@@ -45,22 +61,27 @@ export class AuthService {
|
|||||||
protected authRequestService: AuthRequestService,
|
protected authRequestService: AuthRequestService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected storage: CookieService,
|
protected storage: CookieService,
|
||||||
protected store: Store<AppState>) {
|
protected store: Store<AppState>,
|
||||||
this.store.select(isAuthenticated)
|
protected rdbService: RemoteDataBuildService
|
||||||
.startWith(false)
|
) {
|
||||||
.subscribe((authenticated: boolean) => this._authenticated = authenticated);
|
this.store.pipe(
|
||||||
|
select(isAuthenticated),
|
||||||
|
startWith(false)
|
||||||
|
).subscribe((authenticated: boolean) => this._authenticated = authenticated);
|
||||||
|
|
||||||
// If current route is different from the one setted in authentication guard
|
// If current route is different from the one setted in authentication guard
|
||||||
// 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.select(routerStateSelector)
|
const routeUrl$ = this.store.pipe(
|
||||||
.filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state))
|
select(routerStateSelector),
|
||||||
.filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url))
|
filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state)),
|
||||||
.map((routerState: RouterReducerState) => routerState.state.url);
|
filter((routerState: RouterReducerState) => !this.isLoginRoute(routerState.state.url)),
|
||||||
const redirectUrl$ = this.store.select(getRedirectUrl).distinctUntilChanged();
|
map((routerState: RouterReducerState) => routerState.state.url)
|
||||||
|
);
|
||||||
|
const redirectUrl$ = this.store.pipe(select(getRedirectUrl), distinctUntilChanged());
|
||||||
routeUrl$.pipe(
|
routeUrl$.pipe(
|
||||||
withLatestFrom(redirectUrl$),
|
withLatestFrom(redirectUrl$),
|
||||||
map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl])
|
map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl])
|
||||||
).filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl))
|
).pipe(filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl)))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.clearRedirectUrl();
|
this.clearRedirectUrl();
|
||||||
});
|
});
|
||||||
@@ -93,14 +114,15 @@ export class AuthService {
|
|||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.postToEndpoint('login', body, options)
|
return this.authRequestService.postToEndpoint('login', body, options).pipe(
|
||||||
.map((status: AuthStatus) => {
|
map((status: AuthStatus) => {
|
||||||
|
console.log('yey response');
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status;
|
return status;
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('Invalid email or password'));
|
throw(new Error('Invalid email or password'));
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,28 +131,33 @@ export class AuthService {
|
|||||||
* @returns {Observable<boolean>}
|
* @returns {Observable<boolean>}
|
||||||
*/
|
*/
|
||||||
public isAuthenticated(): Observable<boolean> {
|
public isAuthenticated(): Observable<boolean> {
|
||||||
return this.store.select(isAuthenticated);
|
return this.store.pipe(select(isAuthenticated));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the authenticated user
|
* Returns the authenticated user
|
||||||
* @returns {User}
|
* @returns {User}
|
||||||
*/
|
*/
|
||||||
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
|
public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
|
||||||
// Determine if the user has an existing auth session on the server
|
// Determine if the user has an existing auth session on the server
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Accept', 'application/json');
|
headers = headers.append('Accept', 'application/json');
|
||||||
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.getRequest('status', options)
|
return this.authRequestService.getRequest('status', options).pipe(
|
||||||
.map((status: AuthStatus) => {
|
switchMap((status: AuthStatus) => {
|
||||||
|
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status.eperson;
|
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
|
||||||
|
// Review when https://jira.duraspace.org/browse/DS-4006 is fixed
|
||||||
|
// See https://github.com/DSpace/dspace-angular/issues/292
|
||||||
|
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
|
||||||
|
return person$.pipe(map((eperson) => eperson.payload));
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('Not authenticated'));
|
throw(new Error('Not authenticated'));
|
||||||
}
|
}
|
||||||
});
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,9 +171,10 @@ export class AuthService {
|
|||||||
* Checks if token is present into storage and is not expired
|
* Checks if token is present into storage and is not expired
|
||||||
*/
|
*/
|
||||||
public hasValidAuthenticationToken(): Observable<AuthTokenInfo> {
|
public hasValidAuthenticationToken(): Observable<AuthTokenInfo> {
|
||||||
return this.store.select(getAuthenticationToken)
|
return this.store.pipe(
|
||||||
.take(1)
|
select(getAuthenticationToken),
|
||||||
.map((authTokenInfo: AuthTokenInfo) => {
|
take(1),
|
||||||
|
map((authTokenInfo: AuthTokenInfo) => {
|
||||||
let token: AuthTokenInfo;
|
let token: AuthTokenInfo;
|
||||||
// Retrieve authentication token info and check if is valid
|
// Retrieve authentication token info and check if is valid
|
||||||
token = isNotEmpty(authTokenInfo) ? authTokenInfo : this.storage.get(TOKENITEM);
|
token = isNotEmpty(authTokenInfo) ? authTokenInfo : this.storage.get(TOKENITEM);
|
||||||
@@ -155,7 +183,8 @@ export class AuthService {
|
|||||||
} else {
|
} else {
|
||||||
throw false;
|
throw false;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,14 +196,14 @@ export class AuthService {
|
|||||||
headers = headers.append('Accept', 'application/json');
|
headers = headers.append('Accept', 'application/json');
|
||||||
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.postToEndpoint('login', {}, options)
|
return this.authRequestService.postToEndpoint('login', {}, options).pipe(
|
||||||
.map((status: AuthStatus) => {
|
map((status: AuthStatus) => {
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status.token;
|
return status.token;
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('Not authenticated'));
|
throw(new Error('Not authenticated'));
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,12 +217,12 @@ export class AuthService {
|
|||||||
* Create a new user
|
* Create a new user
|
||||||
* @returns {User}
|
* @returns {User}
|
||||||
*/
|
*/
|
||||||
public create(user: Eperson): Observable<Eperson> {
|
public create(user: EPerson): Observable<EPerson> {
|
||||||
// Normally you would do an HTTP request to POST the user
|
// Normally you would do an HTTP request to POST the user
|
||||||
// details and then return the new user object
|
// details and then return the new user object
|
||||||
// but, let's just return the new user for this example.
|
// but, let's just return the new user for this example.
|
||||||
// this._authenticated = true;
|
// this._authenticated = true;
|
||||||
return Observable.of(user);
|
return observableOf(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,15 +233,15 @@ export class AuthService {
|
|||||||
// Send a request that sign end the session
|
// Send a request that sign end the session
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
const options: HttpOptions = Object.create({headers, responseType: 'text'});
|
const options: HttpOptions = Object.create({ headers, responseType: 'text' });
|
||||||
return this.authRequestService.getRequest('logout', options)
|
return this.authRequestService.getRequest('logout', options).pipe(
|
||||||
.map((status: AuthStatus) => {
|
map((status: AuthStatus) => {
|
||||||
if (!status.authenticated) {
|
if (!status.authenticated) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('auth.errors.invalid-user'));
|
throw(new Error('auth.errors.invalid-user'));
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +262,7 @@ export class AuthService {
|
|||||||
*/
|
*/
|
||||||
public getToken(): AuthTokenInfo {
|
public getToken(): AuthTokenInfo {
|
||||||
let token: AuthTokenInfo;
|
let token: AuthTokenInfo;
|
||||||
this.store.select(getAuthenticationToken)
|
this.store.pipe(select(getAuthenticationToken))
|
||||||
.subscribe((authTokenInfo: AuthTokenInfo) => {
|
.subscribe((authTokenInfo: AuthTokenInfo) => {
|
||||||
// Retrieve authentication token info and check if is valid
|
// Retrieve authentication token info and check if is valid
|
||||||
token = authTokenInfo || null;
|
token = authTokenInfo || null;
|
||||||
@@ -246,9 +275,10 @@ export class AuthService {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public isTokenExpiring(): Observable<boolean> {
|
public isTokenExpiring(): Observable<boolean> {
|
||||||
return this.store.select(isTokenRefreshing)
|
return this.store.pipe(
|
||||||
.first()
|
select(isTokenRefreshing),
|
||||||
.map((isRefreshing: boolean) => {
|
first(),
|
||||||
|
map((isRefreshing: boolean) => {
|
||||||
if (this.isTokenExpired() || isRefreshing) {
|
if (this.isTokenExpired() || isRefreshing) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -256,6 +286,7 @@ export class AuthService {
|
|||||||
return token.expires - (60 * 5 * 1000) < Date.now();
|
return token.expires - (60 * 5 * 1000) < Date.now();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,7 +310,7 @@ export class AuthService {
|
|||||||
|
|
||||||
// Set the cookie expire date
|
// Set the cookie expire date
|
||||||
const expires = new Date(expireDate);
|
const expires = new Date(expireDate);
|
||||||
const options: CookieAttributes = {expires: expires};
|
const options: CookieAttributes = { expires: expires };
|
||||||
|
|
||||||
// Save cookie with the token
|
// Save cookie with the token
|
||||||
return this.storage.set(TOKENITEM, token, options);
|
return this.storage.set(TOKENITEM, token, options);
|
||||||
@@ -324,8 +355,8 @@ export class AuthService {
|
|||||||
* Redirect to the route navigated before the login
|
* Redirect to the route navigated before the login
|
||||||
*/
|
*/
|
||||||
public redirectToPreviousUrl() {
|
public redirectToPreviousUrl() {
|
||||||
this.getRedirectUrl()
|
this.getRedirectUrl().pipe(
|
||||||
.first()
|
first())
|
||||||
.subscribe((redirectUrl) => {
|
.subscribe((redirectUrl) => {
|
||||||
if (isNotEmpty(redirectUrl)) {
|
if (isNotEmpty(redirectUrl)) {
|
||||||
this.clearRedirectUrl();
|
this.clearRedirectUrl();
|
||||||
@@ -337,8 +368,12 @@ export class AuthService {
|
|||||||
this.router.navigated = false;
|
this.router.navigated = false;
|
||||||
const url = decodeURIComponent(redirectUrl);
|
const url = decodeURIComponent(redirectUrl);
|
||||||
this.router.navigateByUrl(url);
|
this.router.navigateByUrl(url);
|
||||||
|
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
|
||||||
|
// this._window.nativeWindow.location.href = url;
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(['/']);
|
||||||
|
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
|
||||||
|
// this._window.nativeWindow.location.href = '/';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -359,9 +394,9 @@ export class AuthService {
|
|||||||
getRedirectUrl(): Observable<string> {
|
getRedirectUrl(): Observable<string> {
|
||||||
const redirectUrl = this.storage.get(REDIRECT_COOKIE);
|
const redirectUrl = this.storage.get(REDIRECT_COOKIE);
|
||||||
if (isNotEmpty(redirectUrl)) {
|
if (isNotEmpty(redirectUrl)) {
|
||||||
return Observable.of(redirectUrl);
|
return observableOf(redirectUrl);
|
||||||
} else {
|
} else {
|
||||||
return this.store.select(getRedirectUrl);
|
return this.store.pipe(select(getRedirectUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +409,7 @@ export class AuthService {
|
|||||||
|
|
||||||
// Set the cookie expire date
|
// Set the cookie expire date
|
||||||
const expires = new Date(expireDate);
|
const expires = new Date(expireDate);
|
||||||
const options: CookieAttributes = {expires: expires};
|
const options: CookieAttributes = { expires: expires };
|
||||||
this.storage.set(REDIRECT_COOKIE, url, options);
|
this.storage.set(REDIRECT_COOKIE, url, options);
|
||||||
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
|
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
|
import {take} from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
|
|
||||||
// reducers
|
// reducers
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
@@ -52,12 +54,12 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
|
|||||||
|
|
||||||
private handleAuth(url: string): Observable<boolean> {
|
private handleAuth(url: string): Observable<boolean> {
|
||||||
// get observable
|
// get observable
|
||||||
const observable = this.store.select(isAuthenticated);
|
const observable = this.store.pipe(select(isAuthenticated));
|
||||||
|
|
||||||
// redirect to sign in page if user is not authenticated
|
// redirect to sign in page if user is not authenticated
|
||||||
observable
|
observable.pipe(
|
||||||
// .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url)
|
// .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url)
|
||||||
.take(1)
|
take(1))
|
||||||
.subscribe((authenticated) => {
|
.subscribe((authenticated) => {
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
this.authService.setRedirectUrl(url);
|
this.authService.setRedirectUrl(url);
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import { AuthError } from './auth-error.model';
|
import { AuthError } from './auth-error.model';
|
||||||
import { AuthTokenInfo } from './auth-token-info.model';
|
import { AuthTokenInfo } from './auth-token-info.model';
|
||||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
import { EPerson } from '../../eperson/models/eperson.model';
|
||||||
import { Eperson } from '../../eperson/models/eperson.model';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
export class AuthStatus {
|
export class AuthStatus {
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export class AuthStatus {
|
|||||||
|
|
||||||
error?: AuthError;
|
error?: AuthError;
|
||||||
|
|
||||||
eperson: Eperson;
|
eperson: Observable<RemoteData<EPerson>>;
|
||||||
|
|
||||||
token?: AuthTokenInfo;
|
token?: AuthTokenInfo;
|
||||||
|
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
import { AuthStatus } from './auth-status.model';
|
import { AuthStatus } from './auth-status.model';
|
||||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||||
import { mapsTo } from '../../cache/builders/build-decorators';
|
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
|
||||||
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
import { Eperson } from '../../eperson/models/eperson.model';
|
import { NormalizedObject } from '../../cache/models/normalized-object.model';
|
||||||
|
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
|
||||||
|
|
||||||
@mapsTo(AuthStatus)
|
@mapsTo(AuthStatus)
|
||||||
@inheritSerialization(NormalizedDSpaceObject)
|
@inheritSerialization(NormalizedObject)
|
||||||
export class NormalizedAuthStatus extends NormalizedDSpaceObject {
|
export class NormalizedAuthStatus extends NormalizedObject {
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@autoserializeAs(new IDToUUIDSerializer('auth-status'), 'id')
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if REST API is up and running, should never return false
|
* True if REST API is up and running, should never return false
|
||||||
@@ -20,7 +26,7 @@ export class NormalizedAuthStatus extends NormalizedDSpaceObject {
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
authenticated: boolean;
|
authenticated: boolean;
|
||||||
|
|
||||||
@autoserializeAs(Eperson)
|
@relationship(ResourceType.EPerson, false)
|
||||||
eperson: Eperson;
|
@autoserialize
|
||||||
|
eperson: string;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
|
import { first, map, switchMap } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
@@ -8,7 +9,8 @@ import { isNotEmpty } from '../../shared/empty.util';
|
|||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { CheckAuthenticationTokenAction } from './auth.actions';
|
import { CheckAuthenticationTokenAction } from './auth.actions';
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
|
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The auth service.
|
* The auth service.
|
||||||
@@ -20,7 +22,7 @@ export class ServerAuthService extends AuthService {
|
|||||||
* Returns the authenticated user
|
* Returns the authenticated user
|
||||||
* @returns {User}
|
* @returns {User}
|
||||||
*/
|
*/
|
||||||
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
|
public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
|
||||||
// Determine if the user has an existing auth session on the server
|
// Determine if the user has an existing auth session on the server
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
@@ -32,14 +34,21 @@ export class ServerAuthService extends AuthService {
|
|||||||
headers = headers.append('X-Forwarded-For', clientIp);
|
headers = headers.append('X-Forwarded-For', clientIp);
|
||||||
|
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.getRequest('status', options)
|
return this.authRequestService.getRequest('status', options).pipe(
|
||||||
.map((status: AuthStatus) => {
|
switchMap((status: AuthStatus) => {
|
||||||
|
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status.eperson;
|
|
||||||
|
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
|
||||||
|
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
|
||||||
|
// person$.subscribe(() => console.log('test'));
|
||||||
|
return person$.pipe(
|
||||||
|
map((eperson) => eperson.payload)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('Not authenticated'));
|
throw(new Error('Not authenticated'));
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,8 +62,8 @@ export class ServerAuthService extends AuthService {
|
|||||||
* Redirect to the route navigated before the login
|
* Redirect to the route navigated before the login
|
||||||
*/
|
*/
|
||||||
public redirectToPreviousUrl() {
|
public redirectToPreviousUrl() {
|
||||||
this.getRedirectUrl()
|
this.getRedirectUrl().pipe(
|
||||||
.first()
|
first())
|
||||||
.subscribe((redirectUrl) => {
|
.subscribe((redirectUrl) => {
|
||||||
if (isNotEmpty(redirectUrl)) {
|
if (isNotEmpty(redirectUrl)) {
|
||||||
// override the route reuse strategy
|
// override the route reuse strategy
|
||||||
|
@@ -1,20 +1,19 @@
|
|||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/Rx';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
|
||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
|
|
||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from '../data/request.models';
|
||||||
import { BrowseEndpointRequest, BrowseEntriesRequest } from '../data/request.models';
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
import { BrowseService } from './browse.service';
|
import { BrowseService } from './browse.service';
|
||||||
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('BrowseService', () => {
|
describe('BrowseService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: BrowseService;
|
let service: BrowseService;
|
||||||
let responseCache: ResponseCacheService;
|
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
|
||||||
@@ -79,22 +78,14 @@ describe('BrowseService', () => {
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
function initMockResponseCacheService(isSuccessful: boolean) {
|
const getRequestEntry$ = (successful: boolean) => {
|
||||||
const rcs = getMockResponseCacheService();
|
return observableOf({
|
||||||
(rcs.get as any).and.returnValue(cold('b-', {
|
response: { isSuccessful: successful, payload: browseDefinitions } as any
|
||||||
b: {
|
} as RequestEntry)
|
||||||
response: {
|
};
|
||||||
isSuccessful,
|
|
||||||
payload: browseDefinitions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
return rcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTestService() {
|
function initTestService() {
|
||||||
return new BrowseService(
|
return new BrowseService(
|
||||||
responseCache,
|
|
||||||
requestService,
|
requestService,
|
||||||
halService,
|
halService,
|
||||||
rdbService
|
rdbService
|
||||||
@@ -108,8 +99,7 @@ describe('BrowseService', () => {
|
|||||||
describe('getBrowseDefinitions', () => {
|
describe('getBrowseDefinitions', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
responseCache = initMockResponseCacheService(true);
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
requestService = getMockRequestService();
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(halService, 'getEndpoint').and
|
spyOn(halService, 'getEndpoint').and
|
||||||
@@ -143,10 +133,11 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseEntriesFor', () => {
|
describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
|
||||||
|
const mockAuthorName = 'Donald Smith';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
responseCache = initMockResponseCacheService(true);
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
requestService = getMockRequestService();
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(service, 'getBrowseDefinitions').and
|
spyOn(service, 'getBrowseDefinitions').and
|
||||||
@@ -156,7 +147,7 @@ describe('BrowseService', () => {
|
|||||||
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
|
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when called with a valid browse definition id', () => {
|
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
|
||||||
it('should configure a new BrowseEntriesRequest', () => {
|
it('should configure a new BrowseEntriesRequest', () => {
|
||||||
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
|
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
|
||||||
|
|
||||||
@@ -175,7 +166,26 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when called with an invalid browse definition id', () => {
|
describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
|
||||||
|
it('should configure a new BrowseItemsRequest', () => {
|
||||||
|
const expected = new BrowseItemsRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items + '?filterValue=' + mockAuthorName);
|
||||||
|
|
||||||
|
scheduler.schedule(() => service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName).subscribe());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||||
|
service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName);
|
||||||
|
|
||||||
|
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getBrowseEntriesFor is called with an invalid browse definition id', () => {
|
||||||
it('should throw an Error', () => {
|
it('should throw an Error', () => {
|
||||||
|
|
||||||
const definitionID = 'invalidID';
|
const definitionID = 'invalidID';
|
||||||
@@ -184,14 +194,23 @@ describe('BrowseService', () => {
|
|||||||
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
|
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when getBrowseItemsFor is called with an invalid browse definition id', () => {
|
||||||
|
it('should throw an Error', () => {
|
||||||
|
|
||||||
|
const definitionID = 'invalidID';
|
||||||
|
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`))
|
||||||
|
|
||||||
|
expect(service.getBrowseItemsFor(definitionID, mockAuthorName)).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseURLFor', () => {
|
describe('getBrowseURLFor', () => {
|
||||||
|
|
||||||
describe('if getBrowseDefinitions fires', () => {
|
describe('if getBrowseDefinitions fires', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
responseCache = initMockResponseCacheService(true);
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
requestService = getMockRequestService();
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(service, 'getBrowseDefinitions').and
|
spyOn(service, 'getBrowseDefinitions').and
|
||||||
@@ -246,8 +265,7 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
describe('if getBrowseDefinitions doesn\'t fire', () => {
|
describe('if getBrowseDefinitions doesn\'t fire', () => {
|
||||||
it('should return undefined', () => {
|
it('should return undefined', () => {
|
||||||
responseCache = initMockResponseCacheService(true);
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
requestService = getMockRequestService();
|
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(service, 'getBrowseDefinitions').and
|
spyOn(service, 'getBrowseDefinitions').and
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
ensureArrayHasValue,
|
ensureArrayHasValue,
|
||||||
@@ -11,12 +11,15 @@ import {
|
|||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SortOptions } from '../cache/models/sort-options.model';
|
import { SortOptions } from '../cache/models/sort-options.model';
|
||||||
import { GenericSuccessResponse } from '../cache/response-cache.models';
|
import { GenericSuccessResponse } from '../cache/response.models';
|
||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { BrowseEndpointRequest, BrowseEntriesRequest, RestRequest } from '../data/request.models';
|
import {
|
||||||
|
BrowseEndpointRequest,
|
||||||
|
BrowseEntriesRequest,
|
||||||
|
BrowseItemsRequest,
|
||||||
|
RestRequest
|
||||||
|
} from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
import { BrowseEntry } from '../shared/browse-entry.model';
|
import { BrowseEntry } from '../shared/browse-entry.model';
|
||||||
@@ -24,11 +27,13 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import {
|
import {
|
||||||
configureRequest,
|
configureRequest,
|
||||||
filterSuccessfulResponses,
|
filterSuccessfulResponses,
|
||||||
|
getBrowseDefinitionLinks,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
getRequestFromSelflink,
|
getRequestFromSelflink
|
||||||
getResponseFromSelflink
|
|
||||||
} from '../shared/operators';
|
} from '../shared/operators';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BrowseService {
|
export class BrowseService {
|
||||||
@@ -48,7 +53,6 @@ export class BrowseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected responseCache: ResponseCacheService,
|
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
private rdb: RemoteDataBuildService,
|
private rdb: RemoteDataBuildService,
|
||||||
@@ -65,16 +69,16 @@ export class BrowseService {
|
|||||||
|
|
||||||
const href$ = request$.pipe(map((request: RestRequest) => request.href));
|
const href$ = request$.pipe(map((request: RestRequest) => request.href));
|
||||||
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
||||||
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
|
const payload$ = requestEntry$.pipe(
|
||||||
const payload$ = responseCache$.pipe(
|
|
||||||
filterSuccessfulResponses(),
|
filterSuccessfulResponses(),
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
|
||||||
map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload),
|
map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload),
|
||||||
ensureArrayHasValue(),
|
ensureArrayHasValue(),
|
||||||
|
map((definitions: BrowseDefinition[]) => definitions
|
||||||
|
.map((definition: BrowseDefinition) => Object.assign(new BrowseDefinition(), definition))),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
|
return this.rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEntriesFor(definitionID: string, options: {
|
getBrowseEntriesFor(definitionID: string, options: {
|
||||||
@@ -82,17 +86,7 @@ export class BrowseService {
|
|||||||
sort?: SortOptions;
|
sort?: SortOptions;
|
||||||
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||||
const request$ = this.getBrowseDefinitions().pipe(
|
const request$ = this.getBrowseDefinitions().pipe(
|
||||||
getRemoteDataPayload(),
|
getBrowseDefinitionLinks(definitionID),
|
||||||
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
|
|
||||||
.find((def: BrowseDefinition) => def.id === definitionID && def.metadataBrowse === true)
|
|
||||||
),
|
|
||||||
map((def: BrowseDefinition) => {
|
|
||||||
if (isNotEmpty(def)) {
|
|
||||||
return def._links;
|
|
||||||
} else {
|
|
||||||
throw new Error(`No metadata browse definition could be found for id '${definitionID}'`);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
map((_links: any) => _links.entries),
|
map((_links: any) => _links.entries),
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
@@ -118,16 +112,72 @@ export class BrowseService {
|
|||||||
const href$ = request$.pipe(map((request: RestRequest) => request.href));
|
const href$ = request$.pipe(map((request: RestRequest) => request.href));
|
||||||
|
|
||||||
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
||||||
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
|
|
||||||
|
|
||||||
const payload$ = responseCache$.pipe(
|
const payload$ = requestEntry$.pipe(
|
||||||
filterSuccessfulResponses(),
|
filterSuccessfulResponses(),
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
|
||||||
map((response: GenericSuccessResponse<BrowseEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
|
map((response: GenericSuccessResponse<BrowseEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
|
||||||
|
map((list: PaginatedList<BrowseEntry>) => Object.assign(list, {
|
||||||
|
page: list.page ? list.page.map((entry: BrowseEntry) => Object.assign(new BrowseEntry(), entry)) : list.page
|
||||||
|
})),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
|
return this.rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all items linked to a certain metadata value
|
||||||
|
* @param {string} definitionID definition ID to define the metadata-field (e.g. author)
|
||||||
|
* @param {string} filterValue metadata value to filter by (e.g. author's name)
|
||||||
|
* @param options Options to narrow down your search:
|
||||||
|
* { pagination: PaginationComponentOptions,
|
||||||
|
* sort: SortOptions }
|
||||||
|
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||||
|
*/
|
||||||
|
getBrowseItemsFor(definitionID: string, filterValue: string, options: {
|
||||||
|
pagination?: PaginationComponentOptions;
|
||||||
|
sort?: SortOptions;
|
||||||
|
} = {}): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
|
const request$ = this.getBrowseDefinitions().pipe(
|
||||||
|
getBrowseDefinitionLinks(definitionID),
|
||||||
|
hasValueOperator(),
|
||||||
|
map((_links: any) => _links.items),
|
||||||
|
hasValueOperator(),
|
||||||
|
map((href: string) => {
|
||||||
|
const args = [];
|
||||||
|
if (isNotEmpty(options.sort)) {
|
||||||
|
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(options.pagination)) {
|
||||||
|
args.push(`page=${options.pagination.currentPage - 1}`);
|
||||||
|
args.push(`size=${options.pagination.pageSize}`);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(filterValue)) {
|
||||||
|
args.push(`filterValue=${filterValue}`);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(args)) {
|
||||||
|
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
|
}
|
||||||
|
return href;
|
||||||
|
}),
|
||||||
|
map((endpointURL: string) => new BrowseItemsRequest(this.requestService.generateRequestId(), endpointURL)),
|
||||||
|
configureRequest(this.requestService)
|
||||||
|
);
|
||||||
|
|
||||||
|
const href$ = request$.pipe(map((request: RestRequest) => request.href));
|
||||||
|
|
||||||
|
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
||||||
|
|
||||||
|
const payload$ = requestEntry$.pipe(
|
||||||
|
filterSuccessfulResponses(),
|
||||||
|
map((response: GenericSuccessResponse<Item[]>) => new PaginatedList(response.pageInfo, response.payload)),
|
||||||
|
map((list: PaginatedList<Item>) => Object.assign(list, {
|
||||||
|
page: list.page ? list.page.map((item: DSpaceObject) => Object.assign(new Item(), item)) : list.page
|
||||||
|
})),
|
||||||
|
distinctUntilChanged()
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseURLFor(metadatumKey: string, linkPath: string): Observable<string> {
|
getBrowseURLFor(metadatumKey: string, linkPath: string): Observable<string> {
|
||||||
|
@@ -1,6 +1,19 @@
|
|||||||
|
import {
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
|
race as observableRace
|
||||||
|
} from 'rxjs';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import {
|
||||||
import { distinctUntilChanged, flatMap, map, startWith } from 'rxjs/operators';
|
distinctUntilChanged,
|
||||||
|
first,
|
||||||
|
flatMap,
|
||||||
|
map,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
takeUntil, tap
|
||||||
|
} from 'rxjs/operators';
|
||||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||||
import { PaginatedList } from '../../data/paginated-list';
|
import { PaginatedList } from '../../data/paginated-list';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
@@ -11,72 +24,63 @@ import { RequestService } from '../../data/request.service';
|
|||||||
|
|
||||||
import { NormalizedObject } from '../models/normalized-object.model';
|
import { NormalizedObject } from '../models/normalized-object.model';
|
||||||
import { ObjectCacheService } from '../object-cache.service';
|
import { ObjectCacheService } from '../object-cache.service';
|
||||||
import { DSOSuccessResponse, ErrorResponse } from '../response-cache.models';
|
import { DSOSuccessResponse, ErrorResponse } from '../response.models';
|
||||||
import { ResponseCacheEntry } from '../response-cache.reducer';
|
|
||||||
import { ResponseCacheService } from '../response-cache.service';
|
|
||||||
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
|
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
|
||||||
import { PageInfo } from '../../shared/page-info.model';
|
import { PageInfo } from '../../shared/page-info.model';
|
||||||
import {
|
import {
|
||||||
|
filterSuccessfulResponses,
|
||||||
getRequestFromSelflink,
|
getRequestFromSelflink,
|
||||||
getResourceLinksFromResponse,
|
getResourceLinksFromResponse
|
||||||
getResponseFromSelflink,
|
|
||||||
filterSuccessfulResponses
|
|
||||||
} from '../../shared/operators';
|
} from '../../shared/operators';
|
||||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RemoteDataBuildService {
|
export class RemoteDataBuildService {
|
||||||
constructor(protected objectCache: ObjectCacheService,
|
constructor(protected objectCache: ObjectCacheService,
|
||||||
protected responseCache: ResponseCacheService,
|
|
||||||
protected requestService: RequestService) {
|
protected requestService: RequestService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> {
|
buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> {
|
||||||
if (typeof href$ === 'string') {
|
if (typeof href$ === 'string') {
|
||||||
href$ = Observable.of(href$);
|
href$ = observableOf(href$);
|
||||||
}
|
}
|
||||||
href$ = href$.multicast(new ReplaySubject(1)).refCount();
|
const requestHref$ = href$.pipe(
|
||||||
|
switchMap((href: string) =>
|
||||||
const requestHref$ = href$.pipe(flatMap((href: string) =>
|
this.objectCache.getRequestHrefBySelfLink(href)),
|
||||||
this.objectCache.getRequestHrefBySelfLink(href)));
|
|
||||||
|
|
||||||
const requestEntry$ = Observable.race(
|
|
||||||
href$.pipe(getRequestFromSelflink(this.requestService)),
|
|
||||||
requestHref$.pipe(getRequestFromSelflink(this.requestService))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseCache$ = Observable.race(
|
const requestEntry$ = observableRace(
|
||||||
href$.pipe(getResponseFromSelflink(this.responseCache)),
|
href$.pipe(getRequestFromSelflink(this.requestService)),
|
||||||
requestHref$.pipe(getResponseFromSelflink(this.responseCache))
|
requestHref$.pipe(getRequestFromSelflink(this.requestService)),
|
||||||
|
).pipe(
|
||||||
|
first()
|
||||||
);
|
);
|
||||||
|
|
||||||
// always use self link if that is cached, only if it isn't, get it via the response.
|
// always use self link if that is cached, only if it isn't, get it via the response.
|
||||||
const payload$ =
|
const payload$ =
|
||||||
Observable.combineLatest(
|
observableCombineLatest(
|
||||||
href$.pipe(
|
href$.pipe(
|
||||||
flatMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href)),
|
switchMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href)),
|
||||||
startWith(undefined)
|
startWith(undefined)),
|
||||||
),
|
requestEntry$.pipe(
|
||||||
responseCache$.pipe(
|
|
||||||
getResourceLinksFromResponse(),
|
getResourceLinksFromResponse(),
|
||||||
flatMap((resourceSelfLinks: string[]) => {
|
switchMap((resourceSelfLinks: string[]) => {
|
||||||
if (isNotEmpty(resourceSelfLinks)) {
|
if (isNotEmpty(resourceSelfLinks)) {
|
||||||
return this.objectCache.getBySelfLink(resourceSelfLinks[0]);
|
return this.objectCache.getBySelfLink(resourceSelfLinks[0]);
|
||||||
} else {
|
} else {
|
||||||
return Observable.of(undefined);
|
return observableOf(undefined);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
startWith(undefined)
|
startWith(undefined)
|
||||||
),
|
)
|
||||||
(fromSelfLink, fromResponse) => {
|
).pipe(
|
||||||
|
map(([fromSelfLink, fromResponse]) => {
|
||||||
if (hasValue(fromSelfLink)) {
|
if (hasValue(fromSelfLink)) {
|
||||||
return fromSelfLink;
|
return fromSelfLink;
|
||||||
} else {
|
} else {
|
||||||
return fromResponse;
|
return fromResponse;
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
).pipe(
|
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
map((normalized: TNormalized) => {
|
map((normalized: TNormalized) => {
|
||||||
return this.build<TNormalized, TDomain>(normalized);
|
return this.build<TNormalized, TDomain>(normalized);
|
||||||
@@ -84,21 +88,21 @@ export class RemoteDataBuildService {
|
|||||||
startWith(undefined),
|
startWith(undefined),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
return this.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
|
return this.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
}
|
}
|
||||||
|
|
||||||
toRemoteDataObservable<T>(requestEntry$: Observable<RequestEntry>, responseCache$: Observable<ResponseCacheEntry>, payload$: Observable<T>) {
|
toRemoteDataObservable<T>(requestEntry$: Observable<RequestEntry>, payload$: Observable<T>) {
|
||||||
return Observable.combineLatest(requestEntry$, responseCache$.startWith(undefined), payload$,
|
return observableCombineLatest(requestEntry$, payload$).pipe(
|
||||||
(reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => {
|
map(([reqEntry, payload]) => {
|
||||||
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
|
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
|
||||||
const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
|
const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
|
||||||
let isSuccessful: boolean;
|
let isSuccessful: boolean;
|
||||||
let error: RemoteDataError;
|
let error: RemoteDataError;
|
||||||
if (hasValue(resEntry) && hasValue(resEntry.response)) {
|
if (hasValue(reqEntry) && hasValue(reqEntry.response)) {
|
||||||
isSuccessful = resEntry.response.isSuccessful;
|
isSuccessful = reqEntry.response.isSuccessful;
|
||||||
const errorMessage = isSuccessful === false ? (resEntry.response as ErrorResponse).errorMessage : undefined;
|
const errorMessage = isSuccessful === false ? (reqEntry.response as ErrorResponse).errorMessage : undefined;
|
||||||
if (hasValue(errorMessage)) {
|
if (hasValue(errorMessage)) {
|
||||||
error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
|
error = new RemoteDataError(reqEntry.response.statusCode, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new RemoteData(
|
return new RemoteData(
|
||||||
@@ -108,37 +112,34 @@ export class RemoteDataBuildService {
|
|||||||
error,
|
error,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> {
|
buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> {
|
||||||
if (typeof href$ === 'string') {
|
if (typeof href$ === 'string') {
|
||||||
href$ = Observable.of(href$);
|
href$ = observableOf(href$);
|
||||||
}
|
}
|
||||||
href$ = href$.shareReplay();
|
|
||||||
|
|
||||||
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
||||||
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
|
const tDomainList$ = requestEntry$.pipe(
|
||||||
|
|
||||||
const tDomainList$ = responseCache$.pipe(
|
|
||||||
getResourceLinksFromResponse(),
|
getResourceLinksFromResponse(),
|
||||||
flatMap((resourceUUIDs: string[]) => {
|
flatMap((resourceUUIDs: string[]) => {
|
||||||
return this.objectCache.getList(resourceUUIDs)
|
return this.objectCache.getList(resourceUUIDs).pipe(
|
||||||
.map((normList: TNormalized[]) => {
|
map((normList: TNormalized[]) => {
|
||||||
return normList.map((normalized: TNormalized) => {
|
return normList.map((normalized: TNormalized) => {
|
||||||
return this.build<TNormalized, TDomain>(normalized);
|
return this.build<TNormalized, TDomain>(normalized);
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}),
|
}),
|
||||||
startWith([]),
|
startWith([]),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
);
|
);
|
||||||
|
const pageInfo$ = requestEntry$.pipe(
|
||||||
const pageInfo$ = responseCache$.pipe(
|
|
||||||
filterSuccessfulResponses(),
|
filterSuccessfulResponses(),
|
||||||
map((entry: ResponseCacheEntry) => {
|
map((response: DSOSuccessResponse) => {
|
||||||
if (hasValue((entry.response as DSOSuccessResponse).pageInfo)) {
|
if (hasValue((response as DSOSuccessResponse).pageInfo)) {
|
||||||
const resPageInfo = (entry.response as DSOSuccessResponse).pageInfo;
|
const resPageInfo = (response as DSOSuccessResponse).pageInfo;
|
||||||
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
|
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
|
||||||
return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
|
return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
|
||||||
} else {
|
} else {
|
||||||
@@ -146,18 +147,19 @@ export class RemoteDataBuildService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const payload$ = Observable.combineLatest(tDomainList$, pageInfo$, (tDomainList, pageInfo) => {
|
const payload$ = observableCombineLatest(tDomainList$, pageInfo$).pipe(
|
||||||
return new PaginatedList(pageInfo, tDomainList);
|
map(([tDomainList, pageInfo]) => {
|
||||||
});
|
return new PaginatedList(pageInfo, tDomainList);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
|
return this.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
}
|
}
|
||||||
|
|
||||||
build<TNormalized, TDomain>(normalized: TNormalized): TDomain {
|
build<TNormalized, TDomain>(normalized: TNormalized): TDomain {
|
||||||
const links: any = {};
|
const links: any = {};
|
||||||
|
|
||||||
const relationships = getRelationships(normalized.constructor) || [];
|
const relationships = getRelationships(normalized.constructor) || [];
|
||||||
|
|
||||||
relationships.forEach((relationship: string) => {
|
relationships.forEach((relationship: string) => {
|
||||||
@@ -200,7 +202,6 @@ export class RemoteDataBuildService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const domainModel = getMapsTo(normalized.constructor);
|
const domainModel = getMapsTo(normalized.constructor);
|
||||||
return Object.assign(new domainModel(), normalized, links);
|
return Object.assign(new domainModel(), normalized, links);
|
||||||
}
|
}
|
||||||
@@ -208,12 +209,11 @@ export class RemoteDataBuildService {
|
|||||||
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
||||||
|
|
||||||
if (isEmpty(input)) {
|
if (isEmpty(input)) {
|
||||||
return Observable.of(new RemoteData(false, false, true, null, []));
|
return observableOf(new RemoteData(false, false, true, null, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observable.combineLatest(
|
return observableCombineLatest(...input).pipe(
|
||||||
...input,
|
map((arr) => {
|
||||||
(...arr: Array<RemoteData<T>>) => {
|
|
||||||
const requestPending: boolean = arr
|
const requestPending: boolean = arr
|
||||||
.map((d: RemoteData<T>) => d.isRequestPending)
|
.map((d: RemoteData<T>) => d.isRequestPending)
|
||||||
.every((b: boolean) => b === true);
|
.every((b: boolean) => b === true);
|
||||||
@@ -255,11 +255,11 @@ export class RemoteDataBuildService {
|
|||||||
error,
|
error,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregatePaginatedList<T>(input: Observable<RemoteData<T[]>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
|
aggregatePaginatedList<T>(input: Observable<RemoteData<T[]>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
return input.map((rd) => Object.assign(rd, {payload: new PaginatedList(pageInfo, rd.payload)}));
|
return input.pipe(map((rd) => Object.assign(rd, { payload: new PaginatedList(pageInfo, rd.payload) })));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,8 @@ import { ResourceType } from '../../shared/resource-type';
|
|||||||
import { NormalizedObject } from './normalized-object.model';
|
import { NormalizedObject } from './normalized-object.model';
|
||||||
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
|
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
|
||||||
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
|
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
|
||||||
|
import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
|
||||||
|
import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
|
||||||
|
|
||||||
export class NormalizedObjectFactory {
|
export class NormalizedObjectFactory {
|
||||||
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
||||||
@@ -33,6 +35,12 @@ export class NormalizedObjectFactory {
|
|||||||
case ResourceType.ResourcePolicy: {
|
case ResourceType.ResourcePolicy: {
|
||||||
return NormalizedResourcePolicy
|
return NormalizedResourcePolicy
|
||||||
}
|
}
|
||||||
|
case ResourceType.EPerson: {
|
||||||
|
return NormalizedEPerson
|
||||||
|
}
|
||||||
|
case ResourceType.Group: {
|
||||||
|
return NormalizedGroup
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
59
src/app/core/cache/object-cache.actions.ts
vendored
59
src/app/core/cache/object-cache.actions.ts
vendored
@@ -2,6 +2,7 @@ import { Action } from '@ngrx/store';
|
|||||||
|
|
||||||
import { type } from '../../shared/ngrx/type';
|
import { type } from '../../shared/ngrx/type';
|
||||||
import { CacheableObject } from './object-cache.reducer';
|
import { CacheableObject } from './object-cache.reducer';
|
||||||
|
import { Operation } from 'fast-json-patch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of ObjectCacheAction type definitions
|
* The list of ObjectCacheAction type definitions
|
||||||
@@ -9,7 +10,9 @@ import { CacheableObject } from './object-cache.reducer';
|
|||||||
export const ObjectCacheActionTypes = {
|
export const ObjectCacheActionTypes = {
|
||||||
ADD: type('dspace/core/cache/object/ADD'),
|
ADD: type('dspace/core/cache/object/ADD'),
|
||||||
REMOVE: type('dspace/core/cache/object/REMOVE'),
|
REMOVE: type('dspace/core/cache/object/REMOVE'),
|
||||||
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS')
|
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS'),
|
||||||
|
ADD_PATCH: type('dspace/core/cache/object/ADD_PATCH'),
|
||||||
|
APPLY_PATCH: type('dspace/core/cache/object/APPLY_PATCH')
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
@@ -54,11 +57,11 @@ export class RemoveFromObjectCacheAction implements Action {
|
|||||||
/**
|
/**
|
||||||
* Create a new RemoveFromObjectCacheAction
|
* Create a new RemoveFromObjectCacheAction
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param href
|
||||||
* the UUID of the object to remove
|
* the unique href of the object to remove
|
||||||
*/
|
*/
|
||||||
constructor(uuid: string) {
|
constructor(href: string) {
|
||||||
this.payload = uuid;
|
this.payload = href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +82,48 @@ export class ResetObjectCacheTimestampsAction implements Action {
|
|||||||
this.payload = newTimestamp;
|
this.payload = newTimestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to add new operations to a specified cached object
|
||||||
|
*/
|
||||||
|
export class AddPatchObjectCacheAction implements Action {
|
||||||
|
type = ObjectCacheActionTypes.ADD_PATCH;
|
||||||
|
payload: {
|
||||||
|
href: string,
|
||||||
|
operations: Operation[]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new AddPatchObjectCacheAction
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* the unique href of the object that should be updated
|
||||||
|
* @param operations
|
||||||
|
* the list of operations to add
|
||||||
|
*/
|
||||||
|
constructor(href: string, operations: Operation[]) {
|
||||||
|
this.payload = { href, operations };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to apply all existing operations to a specified cached object
|
||||||
|
*/
|
||||||
|
export class ApplyPatchObjectCacheAction implements Action {
|
||||||
|
type = ObjectCacheActionTypes.APPLY_PATCH;
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ApplyPatchObjectCacheAction
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* the unique href of the object that should be updated
|
||||||
|
*/
|
||||||
|
constructor(href: string) {
|
||||||
|
this.payload = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,4 +132,6 @@ export class ResetObjectCacheTimestampsAction implements Action {
|
|||||||
export type ObjectCacheAction
|
export type ObjectCacheAction
|
||||||
= AddToObjectCacheAction
|
= AddToObjectCacheAction
|
||||||
| RemoveFromObjectCacheAction
|
| RemoveFromObjectCacheAction
|
||||||
| ResetObjectCacheTimestampsAction;
|
| ResetObjectCacheTimestampsAction
|
||||||
|
| AddPatchObjectCacheAction
|
||||||
|
| ApplyPatchObjectCacheAction;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { provideMockActions } from '@ngrx/effects/testing';
|
import { provideMockActions } from '@ngrx/effects/testing';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import { ObjectCacheEffects } from './object-cache.effects';
|
import { ObjectCacheEffects } from './object-cache.effects';
|
||||||
|
11
src/app/core/cache/object-cache.effects.ts
vendored
11
src/app/core/cache/object-cache.effects.ts
vendored
@@ -1,5 +1,6 @@
|
|||||||
|
import { map } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Actions, Effect } from '@ngrx/effects';
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
|
|
||||||
import { StoreActionTypes } from '../../store.actions';
|
import { StoreActionTypes } from '../../store.actions';
|
||||||
import { ResetObjectCacheTimestampsAction } from './object-cache.actions';
|
import { ResetObjectCacheTimestampsAction } from './object-cache.actions';
|
||||||
@@ -16,9 +17,11 @@ export class ObjectCacheEffects {
|
|||||||
* time ago, and will likely need to be revisited later
|
* time ago, and will likely need to be revisited later
|
||||||
*/
|
*/
|
||||||
@Effect() fixTimestampsOnRehydrate = this.actions$
|
@Effect() fixTimestampsOnRehydrate = this.actions$
|
||||||
.ofType(StoreActionTypes.REHYDRATE)
|
.pipe(ofType(StoreActionTypes.REHYDRATE),
|
||||||
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()));
|
map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()))
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private actions$: Actions) { }
|
constructor(private actions$: Actions) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
25
src/app/core/cache/object-cache.reducer.spec.ts
vendored
25
src/app/core/cache/object-cache.reducer.spec.ts
vendored
@@ -2,8 +2,11 @@ import * as deepFreeze from 'deep-freeze';
|
|||||||
|
|
||||||
import { objectCacheReducer } from './object-cache.reducer';
|
import { objectCacheReducer } from './object-cache.reducer';
|
||||||
import {
|
import {
|
||||||
|
AddPatchObjectCacheAction,
|
||||||
AddToObjectCacheAction,
|
AddToObjectCacheAction,
|
||||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
ApplyPatchObjectCacheAction,
|
||||||
|
RemoveFromObjectCacheAction,
|
||||||
|
ResetObjectCacheTimestampsAction
|
||||||
} from './object-cache.actions';
|
} from './object-cache.actions';
|
||||||
|
|
||||||
class NullAction extends RemoveFromObjectCacheAction {
|
class NullAction extends RemoveFromObjectCacheAction {
|
||||||
@@ -26,7 +29,9 @@ describe('objectCacheReducer', () => {
|
|||||||
},
|
},
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: new Date().getTime(),
|
||||||
msToLive: 900000,
|
msToLive: 900000,
|
||||||
requestHref: selfLink1
|
requestHref: selfLink1,
|
||||||
|
patches: [],
|
||||||
|
isDirty: false
|
||||||
},
|
},
|
||||||
[selfLink2]: {
|
[selfLink2]: {
|
||||||
data: {
|
data: {
|
||||||
@@ -35,7 +40,9 @@ describe('objectCacheReducer', () => {
|
|||||||
},
|
},
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: new Date().getTime(),
|
||||||
msToLive: 900000,
|
msToLive: 900000,
|
||||||
requestHref: selfLink2
|
requestHref: selfLink2,
|
||||||
|
patches: [],
|
||||||
|
isDirty: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deepFreeze(testState);
|
deepFreeze(testState);
|
||||||
@@ -132,4 +139,16 @@ describe('objectCacheReducer', () => {
|
|||||||
objectCacheReducer(testState, action);
|
objectCacheReducer(testState, action);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should perform the ADD_PATCH action without affecting the previous state', () => {
|
||||||
|
const action = new AddPatchObjectCacheAction(selfLink1, [{ op: 'replace', path: '/name', value: 'random string' }]);
|
||||||
|
// testState has already been frozen above
|
||||||
|
objectCacheReducer(testState, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform the APPLY_PATCH action without affecting the previous state', () => {
|
||||||
|
const action = new ApplyPatchObjectCacheAction(selfLink1);
|
||||||
|
// testState has already been frozen above
|
||||||
|
objectCacheReducer(testState, action);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user