mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 06:53:03 +00:00
Merge pull request #123 from wwelling/angular-upgrade
Angular upgrade: this connects to #83 and this connects to #86
This commit is contained in:
124
README.md
124
README.md
@@ -91,13 +91,15 @@ To change the default configuration values, create local files that override the
|
||||
To use the configuration parameters in your component:
|
||||
|
||||
```bash
|
||||
import { GlobalConfig } from "../config";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
|
||||
constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig) {}
|
||||
```
|
||||
|
||||
Running the app
|
||||
---------------
|
||||
|
||||
After you have installed all dependencies you can now run the app. Run `yarn run watch:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`.
|
||||
After you have installed all dependencies you can now run the app. Run `yarn run watch` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`.
|
||||
|
||||
Running in production mode
|
||||
--------------------------
|
||||
@@ -113,7 +115,7 @@ yarn start
|
||||
If you only want to build for production, without starting, run:
|
||||
|
||||
```bash
|
||||
yarn run build:prod:ngc:json
|
||||
yarn run build:prod
|
||||
```
|
||||
|
||||
This will build the application and put the result in the `dist` folder
|
||||
@@ -155,7 +157,7 @@ If you are going to use a remote test enviroment you need to edit the './protrac
|
||||
|
||||
The default browser is Google Chrome.
|
||||
|
||||
Protractor needs a functional instance of the DSpace interface to run the E2E tests, so you need to run:`yarn run watch:dev`
|
||||
Protractor needs a functional instance of the DSpace interface to run the E2E tests, so you need to run:`yarn run watch`
|
||||
|
||||
or any command that bring up the DSpace interface.
|
||||
|
||||
@@ -171,6 +173,17 @@ To run all the tests (e.g.: to run tests with Continuous Integration software) y
|
||||
|
||||
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
||||
|
||||
Deploy
|
||||
------
|
||||
|
||||
```bash
|
||||
# deploy production in standalone pm2 container
|
||||
yarn run deploy
|
||||
|
||||
# remove production from standalone pm2 container
|
||||
yarn run undeploy
|
||||
```
|
||||
|
||||
Other commands
|
||||
--------------
|
||||
|
||||
@@ -204,34 +217,35 @@ File Structure
|
||||
```
|
||||
dspace-angular
|
||||
├── README.md * This document
|
||||
├── app.json * Application manifest file
|
||||
├── app.yaml * Application manifest file
|
||||
├── config * Folder for configuration files
|
||||
│ └── environment.default.js * Default configuration files
|
||||
├── dist * Folder for e2e test files
|
||||
├── e2e *
|
||||
│ ├── environment.default.js * Default configuration files
|
||||
│ └── environment.test.js * Test configuration files
|
||||
├── e2e * Folder for e2e test files
|
||||
│ ├── app.e2e-spec.ts *
|
||||
│ ├── app.po.ts *
|
||||
│ ├── pagenotfound *
|
||||
│ │ ├── pagenotfound.e2e-spec.ts *
|
||||
│ │ └── pagenotfound.po.ts *
|
||||
│ └── tsconfig.json *
|
||||
├── empty.js *
|
||||
├── helpers.js *
|
||||
├── karma.conf.js * Unit Test configuration file
|
||||
│ └── tsconfig.json * TypeScript configuration file for e2e tests
|
||||
├── karma.conf.js * Karma configuration file for Unit Test
|
||||
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
||||
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
||||
├── postcss.config.json * PostCSS (http://postcss.org/) configuration file
|
||||
├── protractor.conf.js * E2E tests configuration file
|
||||
├── postcss.config.js * PostCSS (http://postcss.org/) configuration file
|
||||
├── protractor.conf.js *
|
||||
├── resources * Folder for static resources
|
||||
│ ├── data * Folder for static data
|
||||
│ │ └── en * Folder for i18n English data
|
||||
│ ├── i18n * Folder for i18n translations
|
||||
│ │ └── en.json *
|
||||
│ │ └── en.json * i18n translations for English
|
||||
│ └── images * Folder for images
|
||||
│ └── dspace_logo.png *
|
||||
├── rollup-client.js * Rollup (http://rollupjs.org/) configuration for the client
|
||||
├── rollup-server.js * Rollup (http://rollupjs.org/) configuration for the server
|
||||
│ ├── dspace-logo-old.png *
|
||||
│ ├── dspace-logo.png *
|
||||
│ └── favicon.ico *
|
||||
├── rollup.config.js * Rollup (http://rollupjs.org/) configuration
|
||||
├── spec-bundle.js *
|
||||
├── src * The source of the application
|
||||
│ ├── app * The location of the app module, and root of the application shared by client and server
|
||||
│ ├── app *
|
||||
│ │ ├── app-routing.module.ts *
|
||||
│ │ ├── app.component.html *
|
||||
│ │ ├── app.component.scss *
|
||||
@@ -239,48 +253,62 @@ dspace-angular
|
||||
│ │ ├── app.component.ts *
|
||||
│ │ ├── app.effects.ts *
|
||||
│ │ ├── app.module.ts *
|
||||
│ │ ├── app.reducers.ts *
|
||||
│ │ ├── app.reducer.ts *
|
||||
│ │ ├── browser-app.module.ts * The root module for the client
|
||||
│ │ ├── collection-page *
|
||||
│ │ ├── community-page *
|
||||
│ │ ├── core *
|
||||
│ │ ├── header *
|
||||
│ │ ├── home *
|
||||
│ │ ├── item-page *
|
||||
│ │ ├── object-list *
|
||||
│ │ ├── pagenotfound *
|
||||
│ │ ├── server-app.module.ts * The root module for the server
|
||||
│ │ ├── shared *
|
||||
│ │ └── store.actions.ts *
|
||||
│ │ ├── store.actions.ts *
|
||||
│ │ ├── store.effects.ts *
|
||||
│ │ ├── thumbnail *
|
||||
│ │ └── typings.d.ts * File that allows you to add custom typings for libraries without TypeScript support
|
||||
│ ├── backend * Folder containing a mock of the REST API, hosted by the express server
|
||||
│ │ ├── api.ts *
|
||||
│ │ ├── bitstreams.ts *
|
||||
│ │ ├── bundles.ts *
|
||||
│ │ ├── cache.ts *
|
||||
│ │ ├── collections.ts *
|
||||
│ │ ├── db.ts *
|
||||
│ │ ├── items.ts *
|
||||
│ │ └── metadata.ts *
|
||||
│ ├── client.aot.ts * The bootstrap file for the client, in production
|
||||
│ ├── client.ts * The bootstrap file for the client, during development
|
||||
│ │ ├── data *
|
||||
│ │ └── db.ts *
|
||||
│ ├── config *
|
||||
│ │ ├── cache-config.interface.ts *
|
||||
│ │ ├── global-config.interface.ts *
|
||||
│ │ └── server-config.interface.ts *
|
||||
│ ├── config.ts * File that loads environmental and shareable settings and makes them available to app components
|
||||
│ ├── index.html * The index.html file
|
||||
│ ├── platform *
|
||||
│ │ ├── angular2-meta.ts *
|
||||
│ │ ├── modules *
|
||||
│ │ │ ├── browser.module.ts * The root module for the client
|
||||
│ │ │ └── node.module.ts * The root module for the server
|
||||
│ │ └── workarounds *
|
||||
│ │ ├── __workaround.browser.ts *
|
||||
│ │ └── __workaround.node.ts *
|
||||
│ ├── server.aot.ts * The express (http://expressjs.com/) config and bootstrap file for the server, in production
|
||||
│ ├── server.routes.ts * The routes file for the server
|
||||
│ ├── server.ts * The express (http://expressjs.com/) config and bootstrap file for the server, during development
|
||||
│ ├── styles * Folder containing global styles.
|
||||
│ │ ├── main.scss * Global scss file
|
||||
│ ├── main.browser.ts * The bootstrap file for the client
|
||||
│ ├── main.server.aot.ts * The express (http://expressjs.com/) config and bootstrap file for the server, in production
|
||||
│ ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server, during development
|
||||
│ ├── modules *
|
||||
│ │ ├── cookies *
|
||||
│ │ ├── data-loader *
|
||||
│ │ ├── transfer-http *
|
||||
│ │ ├── transfer-state *
|
||||
│ │ ├── transfer-store *
|
||||
│ │ └── translate-universal-loader.ts *
|
||||
│ ├── routes.ts * The routes file for the server
|
||||
│ ├── styles * Folder containing global styles
|
||||
│ │ ├── _mixins.scss *
|
||||
│ │ └── variables.scss * Global sass variables file
|
||||
│ └── typings.d.ts * File that allows you to add custom typings for libraries without TypeScript support
|
||||
├── tsconfig.aot.json * TypeScript config for production builds
|
||||
├── tsconfig.json * TypeScript config for development build
|
||||
│ ├── tsconfig.browser.json * TypeScript config for the client build
|
||||
│ ├── tsconfig.server.aot.json * TypeScript config for the server build with Ahead of Time
|
||||
│ ├── tsconfig.server.json * TypeScript config for the server build
|
||||
│ └── tsconfig.test.json * TypeScript config for the test build
|
||||
├── tsconfig.json * TypeScript config
|
||||
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
||||
├── typedoc.json * TYPEDOC configuration
|
||||
├── webpack.config.ts * Webpack (https://webpack.github.io/) config for development builds
|
||||
├── webpack.prod.config.ts * Webpack (https://webpack.github.io/) config for production builds
|
||||
├── webpack.test.config.js * Webpack (https://webpack.github.io/) config for testing
|
||||
├── webpack * Webpack (https://webpack.github.io/) config directory
|
||||
│ ├── helpers.js *
|
||||
│ ├── webpack.client.js * Webpack (https://webpack.github.io/) config for client build
|
||||
│ ├── webpack.common.js *
|
||||
│ ├── webpack.prod.js * Webpack (https://webpack.github.io/) config for production build
|
||||
│ ├── webpack.server.js * Webpack (https://webpack.github.io/) config for server build
|
||||
│ └── webpack.test.js * Webpack (https://webpack.github.io/) config for test build
|
||||
├── webpack.config.ts *
|
||||
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
||||
```
|
||||
|
||||
|
12
app.json
12
app.json
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Angular 2 Universal Starter",
|
||||
"description": "Angular 2 Universal starter kit by @AngularClass",
|
||||
"repository": "https://github.com/angular/universal-starter",
|
||||
"logo": "https://cloud.githubusercontent.com/assets/1016365/10639063/138338bc-7806-11e5-8057-d34c75f3cafc.png",
|
||||
"env": {
|
||||
"NPM_CONFIG_PRODUCTION": {
|
||||
"description": "Install `webpack` and other development modules when deploying to allow full builds.",
|
||||
"value": "false"
|
||||
}
|
||||
}
|
||||
}
|
17
app.yaml
Normal file
17
app.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2015-2016, Google, Inc.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# [START app_yaml]
|
||||
runtime: nodejs
|
||||
env: flex
|
||||
# [END app_yaml]
|
@@ -1,28 +1,38 @@
|
||||
module.exports = {
|
||||
// Angular Universal server settings.
|
||||
ui: {
|
||||
ssl: false,
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
nameSpace: '/'
|
||||
},
|
||||
// The REST API server settings.
|
||||
"rest": {
|
||||
"ssl": false,
|
||||
"address": "dspace7.4science.it",
|
||||
"port": 80,
|
||||
rest: {
|
||||
ssl: false,
|
||||
host: 'dspace7.4science.it',
|
||||
port: 80,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
"nameSpace": "/dspace-spring-rest/api"
|
||||
nameSpace: '/dspace-spring-rest/api'
|
||||
},
|
||||
// Angular2 UI server settings.
|
||||
"ui": {
|
||||
"ssl": false,
|
||||
"address": "localhost",
|
||||
"port": 3000,
|
||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||
"nameSpace": "/"
|
||||
// Caching settings
|
||||
cache: {
|
||||
// NOTE: how long should objects be cached for by default
|
||||
msToLive: 15 * 60 * 1000, // 15 minute
|
||||
control: 'max-age=60' // revalidate browser
|
||||
},
|
||||
"cache": {
|
||||
// how long should objects be cached for by default
|
||||
"msToLive": 15 * 60 * 1000, // 15 minute
|
||||
"control": "max-age=60" // revalidate browser
|
||||
// Angular Universal settings
|
||||
universal: {
|
||||
preboot: true,
|
||||
async: true,
|
||||
time: false
|
||||
},
|
||||
"universal": {
|
||||
// Angular Universal settings
|
||||
"preboot": true,
|
||||
"async": true
|
||||
}
|
||||
// Log directory
|
||||
logDirectory: '.',
|
||||
// NOTE: rehydrate or replay
|
||||
// rehydrate will transfer prerender state to browser state, actions do not need to replay
|
||||
// replay will transfer an array of actions to browser, actions replay automatically
|
||||
prerenderStrategy: 'rehydrate',
|
||||
// NOTE: will log all redux actions and transfers in console
|
||||
debug: false
|
||||
};
|
||||
|
3
config/environment.test.js
Normal file
3
config/environment.test.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
|
||||
};
|
@@ -1,6 +1,6 @@
|
||||
import { ProtractorPage } from './app.po';
|
||||
|
||||
describe('protractor App', function() {
|
||||
describe('protractor App', () => {
|
||||
let page: ProtractorPage;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -9,11 +9,11 @@ describe('protractor App', function() {
|
||||
|
||||
it('should display title "DSpace"', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getPageTitleText()).toEqual('DSpace');
|
||||
expect<any>(page.getPageTitleText()).toEqual('DSpace');
|
||||
});
|
||||
|
||||
it('should display header "Welcome to DSpace"', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getFirstHeaderText()).toEqual('Welcome to DSpace');
|
||||
expect<any>(page.getFirstHeaderText()).toEqual('Welcome to DSpace');
|
||||
});
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ProtractorPage } from './pagenotfound.po';
|
||||
|
||||
describe('protractor PageNotFound', function() {
|
||||
describe('protractor PageNotFound', () => {
|
||||
let page: ProtractorPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new ProtractorPage();
|
||||
});
|
||||
|
||||
it('should contain element ds-pagenotfound when navigating to page that doesnt exist"', () => {
|
||||
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
||||
page.navigateToNonExistingPage();
|
||||
expect(page.elementTagExists("ds-pagenotfound")).toEqual(true);
|
||||
expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not contain element ds-pagenotfound when navigating to existing page"', () => {
|
||||
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
|
||||
page.navigateToExistingPage();
|
||||
expect(page.elementTagExists("ds-pagenotfound")).toEqual(false);
|
||||
expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
export class ProtractorPage {
|
||||
HOMEPAGE : string = "/home";
|
||||
NONEXISTINGPAGE : string = "/e9019a69-d4f1-4773-b6a3-bd362caa46f2";
|
||||
HOMEPAGE = '/home';
|
||||
NONEXISTINGPAGE = '/e9019a69-d4f1-4773-b6a3-bd362caa46f2';
|
||||
|
||||
navigateToNonExistingPage() {
|
||||
return browser.get(this.NONEXISTINGPAGE);
|
||||
@@ -11,9 +11,8 @@ export class ProtractorPage {
|
||||
return browser.get(this.HOMEPAGE);
|
||||
}
|
||||
|
||||
elementTagExists(tag : string) {
|
||||
elementTagExists(tag: string) {
|
||||
return element(by.tagName(tag)).isPresent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
7
empty.js
7
empty.js
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
NgProbeToken: {},
|
||||
_createConditionalRootRenderer: function(rootRenderer, extraTokens, coreTokens) {
|
||||
return rootRenderer;
|
||||
},
|
||||
__platform_browser_private__: {}
|
||||
};
|
24
helpers.js
24
helpers.js
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @author: @AngularClass
|
||||
*/
|
||||
var path = require('path');
|
||||
|
||||
// Helper functions
|
||||
var ROOT = path.resolve(__dirname, '.');
|
||||
|
||||
function hasProcessFlag(flag) {
|
||||
return process.argv.join('').indexOf(flag) > -1;
|
||||
}
|
||||
|
||||
function isWebpackDevServer() {
|
||||
return process.argv[1] && !! (/webpack-dev-server/.exec(process.argv[1]));
|
||||
}
|
||||
|
||||
function root(args) {
|
||||
args = Array.prototype.slice.call(arguments, 0);
|
||||
return path.join.apply(path, [ROOT].concat(args));
|
||||
}
|
||||
|
||||
exports.hasProcessFlag = hasProcessFlag;
|
||||
exports.isWebpackDevServer = isWebpackDevServer;
|
||||
exports.root = root;
|
@@ -2,9 +2,11 @@
|
||||
* @author: @AngularClass
|
||||
*/
|
||||
|
||||
module.exports = function(config) {
|
||||
module.exports = function (config) {
|
||||
|
||||
var testWebpackConfig = require('./webpack.test.config.js')({env: 'test'});
|
||||
var testWebpackConfig = require('./webpack/webpack.test.js')({
|
||||
env: 'test'
|
||||
});
|
||||
|
||||
// Uncomment and change to run tests on a remote Selenium server
|
||||
var webdriverConfig = {
|
||||
@@ -15,7 +17,7 @@ module.exports = function(config) {
|
||||
var configuration = {
|
||||
|
||||
// base path that will be used to resolve all patterns (e.g. files, exclude)
|
||||
basePath: '.',
|
||||
basePath: '',
|
||||
|
||||
/*
|
||||
* Frameworks to use
|
||||
@@ -25,15 +27,18 @@ module.exports = function(config) {
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
plugins: [
|
||||
require('karma-webpack'),
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-phantomjs-launcher'),
|
||||
require('karma-webdriver-launcher'),
|
||||
require('karma-coverage'),
|
||||
require('karma-remap-coverage'),
|
||||
require('karma-mocha-reporter'),
|
||||
require('karma-remap-istanbul'),
|
||||
require('karma-sourcemap-loader'),
|
||||
require('karma-webpack')
|
||||
require("istanbul-instrumenter-loader"),
|
||||
require("karma-istanbul-preprocessor")
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
@@ -44,54 +49,60 @@ module.exports = function(config) {
|
||||
*
|
||||
* we are building the test environment in ./spec-bundle.js
|
||||
*/
|
||||
files: [
|
||||
{
|
||||
pattern: './spec-bundle.js',
|
||||
watched: false
|
||||
}
|
||||
],
|
||||
files: [{
|
||||
pattern: './spec-bundle.js',
|
||||
watched: false
|
||||
}],
|
||||
|
||||
/*
|
||||
* preprocess matching files before serving them to the browser
|
||||
* available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
*/
|
||||
preprocessors: {
|
||||
'./spec-bundle.js': ['coverage', 'webpack', 'sourcemap']
|
||||
'./spec-bundle.js': ['istanbul', 'webpack', 'sourcemap']
|
||||
},
|
||||
|
||||
// Webpack Config at ./webpack.test.js
|
||||
webpack: testWebpackConfig,
|
||||
|
||||
// save interim raw coverage report in memory
|
||||
coverageReporter: {
|
||||
reporters: [
|
||||
{
|
||||
type: 'in-memory'
|
||||
}, {
|
||||
type: 'json',
|
||||
subdir: '.',
|
||||
file: 'coverage-final.json'
|
||||
}, {
|
||||
type: 'html',
|
||||
dir: 'coverage/'
|
||||
}
|
||||
]
|
||||
type: 'in-memory'
|
||||
},
|
||||
|
||||
remapCoverageReporter: {
|
||||
'text-summary': null,
|
||||
json: './coverage/coverage.json',
|
||||
html: './coverage/html'
|
||||
'text-summary': null, // to show summary in console
|
||||
html: './coverage/html',
|
||||
cobertura: './coverage/cobertura.xml'
|
||||
},
|
||||
|
||||
remapIstanbulReporter: {
|
||||
remapOptions: {}, //additional remap options
|
||||
reports: {
|
||||
html: 'coverage'
|
||||
json: 'coverage/coverage.json',
|
||||
lcovonly: 'coverage/lcov.info',
|
||||
html: 'coverage/html/',
|
||||
}
|
||||
},
|
||||
|
||||
// Webpack please don't spam the console when running in karma!
|
||||
/**
|
||||
* Webpack please don't spam the console when running in karma!
|
||||
*/
|
||||
webpackMiddleware: {
|
||||
stats: 'errors-only'
|
||||
/**
|
||||
* webpack-dev-middleware configuration
|
||||
* i.e.
|
||||
*/
|
||||
noInfo: true,
|
||||
/**
|
||||
* and use stats to turn off verbose output
|
||||
*/
|
||||
stats: {
|
||||
/**
|
||||
* options i.e.
|
||||
*/
|
||||
chunks: false
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -100,9 +111,7 @@ module.exports = function(config) {
|
||||
* possible values: 'dots', 'progress'
|
||||
* available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
*/
|
||||
reporters: [
|
||||
'mocha', 'coverage', 'karma-remap-istanbul'
|
||||
],
|
||||
reporters: ['mocha', 'coverage', 'remap-coverage', 'karma-remap-istanbul'],
|
||||
|
||||
// Karma web server port
|
||||
port: 9876,
|
||||
@@ -114,10 +123,10 @@ module.exports = function(config) {
|
||||
* level of logging
|
||||
* possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
*/
|
||||
logLevel: config.LOG_INFO,
|
||||
logLevel: config.LOG_WARN,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
//autoWatch: true,
|
||||
autoWatch: false,
|
||||
|
||||
/*
|
||||
* start these browsers
|
||||
@@ -125,9 +134,6 @@ module.exports = function(config) {
|
||||
*/
|
||||
browsers: [
|
||||
'Chrome'
|
||||
//'ChromeTravisCi',
|
||||
//'SeleniumChrome',
|
||||
//'SeleniumFirefox'
|
||||
],
|
||||
|
||||
customLaunchers: {
|
||||
@@ -156,11 +162,6 @@ module.exports = function(config) {
|
||||
|
||||
browserNoActivityTimeout: 30000
|
||||
|
||||
/*
|
||||
* Continuous Integration mode
|
||||
* if true, Karma captures browsers, runs the tests and exits
|
||||
*/
|
||||
//singleRun: true
|
||||
};
|
||||
|
||||
if (process.env.TRAVIS) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"watch": [
|
||||
"dist",
|
||||
"config",
|
||||
"src/index.html"
|
||||
],
|
||||
"ext": "js ts json html"
|
||||
|
303
package.json
303
package.json
@@ -1,187 +1,200 @@
|
||||
{
|
||||
"name": "dspace-angular",
|
||||
"version": "0.0.0",
|
||||
"description": "Angular 2 Universal UI for DSpace",
|
||||
"version": "0.0.1",
|
||||
"description": "Angular Universal UI for DSpace",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dspace/dspace-angular.git"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean:log": "rimraf *.log*",
|
||||
"clean:dist": "rimraf dist/*",
|
||||
"clean:node": "rimraf node_modules",
|
||||
"clean:ngc": "rimraf **/*.ngfactory.ts",
|
||||
"clean:json": "rimraf *.records.json",
|
||||
"clean:css": "rimraf src/**/*.css",
|
||||
"clean:css:ts": "rimraf src/**/*.css.ts",
|
||||
"clean:scss:ts": "rimraf src/**/*.scss.ts",
|
||||
"clean:css:shim:ts": "rimraf src/**/*.css.shim.ts",
|
||||
"clean:scss:shim:ts": "rimraf src/**/*.scss.shim.ts",
|
||||
"global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup",
|
||||
"clean:coverage": "rimraf coverage",
|
||||
"clean:prod": "yarn run clean:ngc && yarn run clean:json && yarn run clean:css && yarn run clean:css:ts && yarn run clean:scss:ts && yarn run clean:css:shim:ts && yarn run clean:scss:shim:ts && yarn run clean:dist",
|
||||
"clean": "yarn run clean:log && yarn run clean:prod && yarn run clean:coverage && yarn run clean:node",
|
||||
"sass": "node-sass src -o src --include-path node_modules --output-style compressed -q",
|
||||
"postcss": "node node_modules/postcss-cli/bin/postcss -c postcss.config.json",
|
||||
"style": "yarn run sass && yarn run postcss",
|
||||
"style:watch": "nodemon -e scss -w src -x \"yarn run style\"",
|
||||
"rollup": "rollup -c rollup-server.js && rollup -c rollup-client.js",
|
||||
"prebuild": "yarn run clean:dist && yarn run style",
|
||||
"clean:dist": "rimraf dist",
|
||||
"clean:doc": "rimraf doc",
|
||||
"clean:log": "rimraf *.log*",
|
||||
"clean:json": "rimraf *.records.json",
|
||||
"clean:node": "rimraf node_modules",
|
||||
"clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json ",
|
||||
"clean": "yarn run clean:prod && yarn run clean:node",
|
||||
"prebuild": "yarn run clean:dist",
|
||||
"prebuild:aot": "yarn run prebuild",
|
||||
"prebuild:prod": "yarn run prebuild",
|
||||
"build": "webpack --progress",
|
||||
"build:prod": "webpack --config webpack.prod.config.ts",
|
||||
"build:prod:rollup": "yarn run build:prod && yarn run rollup",
|
||||
"build:prod:ngc": "yarn run clean:prod && yarn run style && yarn run ngc && yarn run build:prod:rollup",
|
||||
"build:prod:ngc:json": "yarn run clean:prod && yarn run style && yarn run ngc && yarn run build:prod:json:rollup",
|
||||
"build:prod:json": "webpack --config webpack.prod.config.ts --json | webpack-bundle-size-analyzer",
|
||||
"build:prod:json:rollup": "yarn run build:prod:json && yarn run rollup",
|
||||
"ngc": "ngc -p tsconfig.aot.json",
|
||||
"prestart": "yarn run build:prod:ngc:json",
|
||||
"server": "node dist/server/index.js",
|
||||
"server:dev": "node dist/server/index.js",
|
||||
"server:dev:watch": "nodemon --debug dist/server/index.js",
|
||||
"build:aot": "webpack --env.aot --env.server && webpack --env.aot --env.client",
|
||||
"build:prod": "webpack --env.aot --env.server -p && webpack --env.aot --env.client -p",
|
||||
"postbuild:prod": "yarn run rollup",
|
||||
"rollup": "rollup -c rollup.config.js",
|
||||
"prestart": "yarn run build:prod",
|
||||
"prestart:dev": "yarn run build",
|
||||
"start": "yarn run server",
|
||||
"start:dev": "yarn run clean:prod && yarn run build && yarn run server:dev",
|
||||
"watch": "webpack -w & yarn run style:watch",
|
||||
"watch:dev:server": "npm-run-all -p server:dev:watch watch",
|
||||
"watch:dev": "yarn run clean:prod && yarn run build && yarn run watch:dev:server",
|
||||
"watch:prod:server": "npm-run-all -p server watch",
|
||||
"watch:prod": "yarn run build:prod:ngc:json && yarn run watch:prod:server",
|
||||
"start:dev": "yarn run server",
|
||||
"deploy": "pm2 start dist/server.js",
|
||||
"predeploy": "npm run build:prod",
|
||||
"preundeploy": "pm2 stop dist/server.js",
|
||||
"undeploy": "pm2 delete dist/server.js",
|
||||
"postundeploy": "npm run clean:dist",
|
||||
"server": "node dist/server.js",
|
||||
"server:watch": "nodemon dist/server.js",
|
||||
"server:watch:debug": "nodemon --debug dist/server.js",
|
||||
"webpack:watch": "webpack -w",
|
||||
"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: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: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",
|
||||
"debug": "node --debug-brk dist/server/index.js",
|
||||
"debug:server": "node-nightly --inspect --debug-brk dist/server/index.js",
|
||||
"debug:start": "yarn run build && yarn run debug:server",
|
||||
"predebug:server": "yarn run build",
|
||||
"debug": "node --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:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --config webpack.prod.config.ts",
|
||||
"docs": "typedoc --options typedoc.json ./src/",
|
||||
"lint": "tslint \"src/**/*.ts\" || true",
|
||||
"global": "npm install -g @angular/cli nodemon npm-check-updates rimraf ts-node typedoc typescript webpack webpack-bundle-size-analyzer rollup marked node-gyp",
|
||||
"ci": "yarn run lint && yarn run build:prod:ngc:json && yarn run test && npm-run-all -p -r server e2e",
|
||||
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server -p",
|
||||
"ci": "yarn run lint && yarn run build:aot && yarn run test && npm-run-all -p -r server e2e",
|
||||
"protractor": "node node_modules/protractor/bin/protractor",
|
||||
"pree2e": "yarn run webdriver:update",
|
||||
"e2e": "yarn run protractor",
|
||||
"test": "karma start --single-run",
|
||||
"test:watch": "karma start --no-single-run --auto-watch",
|
||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage",
|
||||
"webdriver:start": "node node_modules/protractor/bin/webdriver-manager start --seleniumPort 4444",
|
||||
"webdriver:update": "node node_modules/protractor/bin/webdriver-manager update --standalone"
|
||||
"webdriver:update": "node node_modules/protractor/bin/webdriver-manager update --standalone",
|
||||
"lint": "tslint \"src/**/*.ts\" || true && tslint \"e2e/**/*.ts\" || true",
|
||||
"docs": "typedoc --options typedoc.json ./src/",
|
||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/common": "2.2.3",
|
||||
"@angular/compiler": "2.2.3",
|
||||
"@angular/compiler-cli": "2.2.3",
|
||||
"@angular/core": "2.2.3",
|
||||
"@angular/forms": "2.2.3",
|
||||
"@angular/http": "2.2.3",
|
||||
"@angular/platform-browser": "2.2.3",
|
||||
"@angular/platform-browser-dynamic": "2.2.3",
|
||||
"@angular/platform-server": "2.2.3",
|
||||
"@angular/router": "3.2.3",
|
||||
"@angular/upgrade": "2.2.3",
|
||||
"@angular/animations": "4.3.1",
|
||||
"@angular/common": "4.3.1",
|
||||
"@angular/core": "4.3.1",
|
||||
"@angular/forms": "4.3.1",
|
||||
"@angular/http": "4.3.1",
|
||||
"@angular/platform-browser": "4.3.1",
|
||||
"@angular/platform-browser-dynamic": "4.3.1",
|
||||
"@angular/platform-server": "4.3.1",
|
||||
"@angular/router": "4.3.1",
|
||||
"@angularclass/bootloader": "1.0.1",
|
||||
"@angularclass/idle-preload": "1.0.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.18",
|
||||
"@ngrx/core": "^1.2.0",
|
||||
"@ngrx/effects": "2.0.2",
|
||||
"@ngrx/router-store": "^1.2.5",
|
||||
"@ngrx/store": "^2.2.1",
|
||||
"@ngrx/store-devtools": "^3.2.2",
|
||||
"@ngx-translate/core": "^6.0.1",
|
||||
"@ngx-translate/http-loader": "^0.0.3",
|
||||
"@types/jsonschema": "0.0.5",
|
||||
"angular2-express-engine": "2.1.0-rc.1",
|
||||
"angular2-platform-node": "2.1.0-rc.1",
|
||||
"angular2-universal": "2.1.0-rc.1",
|
||||
"angular2-universal-polyfills": "2.1.0-rc.1",
|
||||
"body-parser": "1.15.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.28",
|
||||
"@ngrx/core": "1.2.0",
|
||||
"@ngrx/effects": "2.0.4",
|
||||
"@ngrx/router-store": "1.2.6",
|
||||
"@ngrx/store": "2.2.3",
|
||||
"@nguniversal/express-engine": "1.0.0-beta.2",
|
||||
"@ngx-translate/core": "7.1.0",
|
||||
"@ngx-translate/http-loader": "0.1.0",
|
||||
"body-parser": "1.17.2",
|
||||
"bootstrap": "4.0.0-alpha.6",
|
||||
"cerialize": "^0.1.13",
|
||||
"compression": "1.6.2",
|
||||
"express": "4.14.0",
|
||||
"cerialize": "0.1.15",
|
||||
"compression": "1.7.0",
|
||||
"cookie-parser": "1.4.3",
|
||||
"core-js": "2.4.1",
|
||||
"express": "4.15.3",
|
||||
"express-session": "1.15.4",
|
||||
"font-awesome": "4.7.0",
|
||||
"http-server": "^0.9.0",
|
||||
"http-server": "0.10.0",
|
||||
"https": "1.0.0",
|
||||
"js.clone": "0.0.3",
|
||||
"jsonschema": "^1.1.1",
|
||||
"jsonschema": "1.1.1",
|
||||
"methods": "1.1.2",
|
||||
"morgan": "1.7.0",
|
||||
"ng2-pagination": "^2.0.0",
|
||||
"preboot": "4.5.2",
|
||||
"reflect-metadata": "^0.1.10",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"ts-md5": "^1.2.0",
|
||||
"webfontloader": "1.6.27",
|
||||
"zone.js": "0.6.26"
|
||||
"morgan": "1.8.2",
|
||||
"ngx-pagination": "3.0.1",
|
||||
"pem": "1.9.7",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"rxjs": "5.4.2",
|
||||
"ts-md5": "1.2.0",
|
||||
"webfontloader": "1.6.28",
|
||||
"zone.js": "0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ngtools/webpack": "1.1.9",
|
||||
"@types/body-parser": "0.0.33",
|
||||
"@types/compression": "0.0.33",
|
||||
"@angular/compiler": "4.3.1",
|
||||
"@angular/compiler-cli": "4.3.1",
|
||||
"@ngrx/store-devtools": "3.2.4",
|
||||
"@ngtools/webpack": "1.5.1",
|
||||
"@types/cookie-parser": "1.3.30",
|
||||
"@types/deep-freeze": "0.0.29",
|
||||
"@types/express": "4.0.34",
|
||||
"@types/express-serve-static-core": "4.0.39",
|
||||
"@types/hammerjs": "2.0.33",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"@types/deep-freeze": "0.1.1",
|
||||
"@types/express": "4.0.36",
|
||||
"@types/express-serve-static-core": "4.0.49",
|
||||
"@types/hammerjs": "2.0.34",
|
||||
"@types/jasmine": "2.5.53",
|
||||
"@types/lodash": "ts2.0",
|
||||
"@types/memory-cache": "0.0.29",
|
||||
"@types/mime": "0.0.29",
|
||||
"@types/morgan": "1.7.32",
|
||||
"@types/node": "6.0.52",
|
||||
"@types/mime": "1.3.1",
|
||||
"@types/node": "8.0.14",
|
||||
"@types/serve-static": "1.7.31",
|
||||
"@types/webfontloader": "1.6.27",
|
||||
"ajv": "4.2.0",
|
||||
"ajv-keywords": "1.1.1",
|
||||
"angular2-template-loader": "0.6.0",
|
||||
"autoprefixer": "6.5.4",
|
||||
"awesome-typescript-loader": "2.2.4",
|
||||
"codelyzer": "2.0.0-beta.3",
|
||||
"cookie-parser": "1.4.3",
|
||||
"@types/source-map": "0.5.0",
|
||||
"@types/webfontloader": "1.6.28",
|
||||
"ajv": "5.2.2",
|
||||
"ajv-keywords": "2.1.0",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"autoprefixer": "7.1.2",
|
||||
"awesome-typescript-loader": "3.2.1",
|
||||
"codelyzer": "3.1.2",
|
||||
"compression-webpack-plugin": "1.0.0",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"css-loader": "^0.26.0",
|
||||
"css-loader": "0.28.4",
|
||||
"deep-freeze": "0.0.1",
|
||||
"html-webpack-plugin": "^2.21.0",
|
||||
"imports-loader": "0.7.0",
|
||||
"istanbul-instrumenter-loader": "^0.2.0",
|
||||
"jasmine-core": "~2.5.2",
|
||||
"jasmine-spec-reporter": "~2.7.0",
|
||||
"exports-loader": "0.6.4",
|
||||
"html-webpack-plugin": "2.29.0",
|
||||
"imports-loader": "0.7.1",
|
||||
"istanbul-instrumenter-loader": "2.0.0",
|
||||
"jasmine-core": "2.6.4",
|
||||
"jasmine-spec-reporter": "4.1.1",
|
||||
"json-loader": "0.5.4",
|
||||
"karma": "^1.2.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-mocha-reporter": "^2.0.0",
|
||||
"karma-phantomjs-launcher": "^1.0.2",
|
||||
"karma-remap-istanbul": "^0.2.1",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webdriver-launcher": "^1.0.4",
|
||||
"karma-webpack": "1.8.0",
|
||||
"ngrx-store-freeze": "^0.1.9",
|
||||
"node-sass": "4.0.0",
|
||||
"karma": "1.7.0",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-coverage": "1.1.1",
|
||||
"karma-istanbul-preprocessor": "0.0.2",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-mocha-reporter": "2.2.3",
|
||||
"karma-phantomjs-launcher": "1.0.4",
|
||||
"karma-remap-coverage": "0.1.4",
|
||||
"karma-remap-istanbul": "0.6.0",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-webdriver-launcher": "1.0.5",
|
||||
"karma-webpack": "2.0.4",
|
||||
"ngrx-store-freeze": "0.1.9",
|
||||
"node-sass": "4.5.3",
|
||||
"nodemon": "1.11.0",
|
||||
"npm-run-all": "4.0.2",
|
||||
"postcss-cli": "^2.6.0",
|
||||
"protractor": "~4.0.14",
|
||||
"protractor-istanbul-plugin": "~2.0.0",
|
||||
"postcss": "6.0.8",
|
||||
"postcss-apply": "0.8.0",
|
||||
"postcss-cli": "4.1.0",
|
||||
"postcss-cssnext": "3.0.2",
|
||||
"postcss-loader": "2.0.6",
|
||||
"postcss-responsive-type": "1.0.0",
|
||||
"postcss-smart-import": "0.7.5",
|
||||
"protractor": "5.1.2",
|
||||
"protractor-istanbul-plugin": "2.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"rimraf": "2.5.4",
|
||||
"rollup": "0.37.0",
|
||||
"rollup-plugin-commonjs": "6.0.0",
|
||||
"resolve-url-loader": "2.1.0",
|
||||
"rimraf": "2.6.1",
|
||||
"rollup": "0.45.2",
|
||||
"rollup-plugin-commonjs": "8.0.2",
|
||||
"rollup-plugin-node-globals": "1.1.0",
|
||||
"rollup-plugin-node-resolve": "2.0.0",
|
||||
"rollup-plugin-uglify": "1.0.1",
|
||||
"source-map-loader": "^0.1.5",
|
||||
"string-replace-loader": "1.0.5",
|
||||
"to-string-loader": "^1.1.4",
|
||||
"rollup-plugin-node-resolve": "3.0.0",
|
||||
"rollup-plugin-uglify": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"script-ext-html-webpack-plugin": "1.8.5",
|
||||
"source-map-loader": "0.2.1",
|
||||
"string-replace-loader": "1.3.0",
|
||||
"to-string-loader": "1.1.5",
|
||||
"ts-helpers": "1.1.2",
|
||||
"ts-node": "1.7.2",
|
||||
"tslint": "4.0.2",
|
||||
"tslint-loader": "3.3.0",
|
||||
"typedoc": "0.5.7",
|
||||
"typescript": "2.0.10",
|
||||
"v8-lazy-parse-webpack-plugin": "0.3.0",
|
||||
"webpack": "2.1.0-beta.27",
|
||||
"webpack-bundle-analyzer": "1.4.1",
|
||||
"webpack-dev-middleware": "1.9.0",
|
||||
"webpack-dev-server": "2.1.0-beta.11",
|
||||
"webpack-merge": "1.1.1"
|
||||
"ts-node": "3.2.0",
|
||||
"tslint": "5.1.0",
|
||||
"tslint-loader": "3.5.3",
|
||||
"typedoc": "0.7.1",
|
||||
"typescript": "2.4.1",
|
||||
"webpack": "3.3.0",
|
||||
"webpack-bundle-analyzer": "2.8.3",
|
||||
"webpack-dev-middleware": "1.11.0",
|
||||
"webpack-dev-server": "2.5.1",
|
||||
"webpack-merge": "4.1.0",
|
||||
"webpack-node-externals": "1.6.0"
|
||||
}
|
||||
}
|
||||
|
8
postcss.config.js
Normal file
8
postcss.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-smart-import')(),
|
||||
require('postcss-cssnext')(),
|
||||
require('postcss-apply')(),
|
||||
require('postcss-responsive-type')()
|
||||
]
|
||||
};
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"use": ["autoprefixer"],
|
||||
"input": "src/**/*.css",
|
||||
"replace": true,
|
||||
"local-plugins": true,
|
||||
"autoprefixer": {
|
||||
"browsers": "last 2 versions"
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js
|
||||
|
||||
/*global jasmine */
|
||||
var SpecReporter = require('jasmine-spec-reporter');
|
||||
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
@@ -28,11 +28,11 @@ exports.config = {
|
||||
// -----------------------------------------------------------------
|
||||
// Browser and Capabilities: Chrome
|
||||
// -----------------------------------------------------------------
|
||||
capabilities: {
|
||||
'browserName': 'chrome',
|
||||
'version': '',
|
||||
'platform': 'ANY'
|
||||
},
|
||||
capabilities: {
|
||||
'browserName': 'chrome',
|
||||
'version': '',
|
||||
'platform': 'ANY'
|
||||
},
|
||||
// -----------------------------------------------------------------
|
||||
// Browser and Capabilities: Firefox
|
||||
// -----------------------------------------------------------------
|
||||
@@ -63,7 +63,7 @@ exports.config = {
|
||||
// }
|
||||
//],
|
||||
|
||||
plugins : [{
|
||||
plugins: [{
|
||||
path: 'node_modules/protractor-istanbul-plugin'
|
||||
}],
|
||||
|
||||
@@ -71,15 +71,19 @@ exports.config = {
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
print: function () {}
|
||||
},
|
||||
useAllAngular2AppRoots: true,
|
||||
beforeLaunch: function() {
|
||||
beforeLaunch: function () {
|
||||
require('ts-node').register({
|
||||
project: 'e2e'
|
||||
});
|
||||
},
|
||||
onPrepare: function() {
|
||||
jasmine.getEnv().addReporter(new SpecReporter());
|
||||
onPrepare: function () {
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: true
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
0
resources/data/.gitkeep
Normal file
0
resources/data/.gitkeep
Normal file
0
resources/data/en/.gitkeep
Normal file
0
resources/data/en/.gitkeep
Normal file
5
resources/data/en/test.json
Normal file
5
resources/data/en/test.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"test": {
|
||||
"message": "Hello, DSpace!"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
resources/images/dspace-logo.png
Normal file
BIN
resources/images/dspace-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
BIN
resources/images/favicon.ico
Normal file
BIN
resources/images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@@ -1,16 +0,0 @@
|
||||
import rollup from 'rollup'
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
|
||||
export default {
|
||||
entry : 'dist/client/main.bundle.js',
|
||||
dest : 'dist/client/main.bundle.js',
|
||||
sourceMap : false,
|
||||
format : 'iife',
|
||||
plugins : [
|
||||
nodeResolve({jsnext: true, module: true}),
|
||||
commonjs({include: 'node_modules/rxjs/**'}),
|
||||
uglify()
|
||||
]
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import rollup from 'rollup'
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
|
||||
export default {
|
||||
entry : 'dist/server/index.js',
|
||||
dest : 'dist/server/index.js',
|
||||
sourceMap : false,
|
||||
format : 'iife',
|
||||
plugins : [
|
||||
nodeResolve({jsnext: true, module: true}),
|
||||
commonjs({include: 'node_modules/rxjs/**'}),
|
||||
uglify()
|
||||
]
|
||||
}
|
20
rollup.config.js
Normal file
20
rollup.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
|
||||
export default {
|
||||
entry: 'dist/client.js',
|
||||
dest: 'dist/client.js',
|
||||
sourceMap: false,
|
||||
format: 'iife',
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
jsnext: true,
|
||||
module: true
|
||||
}),
|
||||
commonjs({
|
||||
include: 'node_modules/rxjs/**'
|
||||
}),
|
||||
uglify()
|
||||
]
|
||||
}
|
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
|
@@ -1,4 +1,13 @@
|
||||
@import '../styles/variables.scss';
|
||||
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import "../../node_modules/font-awesome/scss/font-awesome.scss";
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
// Sticky Footer
|
||||
|
||||
.outer-wrapper {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
|
@@ -5,24 +5,33 @@ import {
|
||||
inject,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
DebugElement
|
||||
} from "@angular/core";
|
||||
} from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
|
||||
import { Store, StoreModule } from "@ngrx/store";
|
||||
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
|
||||
// Load the implementations that should be tested
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HostWindowState } from "./shared/host-window.reducer";
|
||||
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
import { MockTranslateLoader } from './shared/testing/mock-translate-loader';
|
||||
|
||||
import { GLOBAL_CONFIG, EnvConfig } from '../config';
|
||||
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||
import { BrowserCookiesModule } from '../modules/cookies/browser-cookies.module';
|
||||
import { BrowserDataLoaderModule } from '../modules/data-loader/browser-data-loader.module';
|
||||
import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module';
|
||||
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
|
||||
|
||||
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
|
||||
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -34,15 +43,23 @@ describe('App component', () => {
|
||||
// async beforeEach
|
||||
beforeEach(async(() => {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: MockTranslateLoader
|
||||
}
|
||||
})],
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.provideStore({}),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: MockTranslateLoader
|
||||
}
|
||||
}),
|
||||
BrowserCookiesModule,
|
||||
BrowserDataLoaderModule,
|
||||
BrowserTransferStateModule,
|
||||
BrowserTransferStoreModule
|
||||
],
|
||||
declarations: [AppComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: GLOBAL_CONFIG, useValue: EnvConfig },
|
||||
{ provide: GLOBAL_CONFIG, useValue: ENV_CONFIG },
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
AppComponent
|
||||
],
|
||||
@@ -56,7 +73,7 @@ describe('App component', () => {
|
||||
|
||||
comp = fixture.componentInstance; // component test instance
|
||||
|
||||
// query for the <div class="outer-wrapper"> by CSS element selector
|
||||
// query for the <div class='outer-wrapper'> by CSS element selector
|
||||
de = fixture.debugElement.query(By.css('div.outer-wrapper'));
|
||||
el = de.nativeElement;
|
||||
});
|
||||
@@ -66,7 +83,7 @@ describe('App component', () => {
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe("when the window is resized", () => {
|
||||
describe('when the window is resized', () => {
|
||||
let width: number;
|
||||
let height: number;
|
||||
let store: Store<HostWindowState>;
|
||||
@@ -80,7 +97,7 @@ describe('App component', () => {
|
||||
height = window.innerHeight;
|
||||
});
|
||||
|
||||
it("should dispatch a HostWindowResizeAction with the width and height of the window as its payload", () => {
|
||||
it('should dispatch a HostWindowResizeAction with the width and height of the window as its payload', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new HostWindowResizeAction(width, height));
|
||||
});
|
||||
|
||||
|
@@ -1,31 +1,40 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
ViewEncapsulation,
|
||||
OnInit, HostListener
|
||||
} from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { HostWindowState } from "./shared/host-window.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
OnInit,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
|
||||
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||
import { EnvConfig, GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { TransferState } from '../modules/transfer-state/transfer-state';
|
||||
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
|
||||
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'ds-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
styleUrls: ['./app.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
|
||||
@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
|
||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||
private translate: TranslateService,
|
||||
private cache: TransferState,
|
||||
private store: Store<HostWindowState>
|
||||
) {
|
||||
// this language will be used as a fallback when a translation isn't found in the current language
|
||||
@@ -34,11 +43,20 @@ export class AppComponent implements OnInit {
|
||||
translate.use('en');
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
this.syncCache();
|
||||
}
|
||||
|
||||
syncCache() {
|
||||
this.store.take(1).subscribe((state: HostWindowState) => {
|
||||
this.cache.set('state', state);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.onInit();
|
||||
const env: string = EnvConfig.production ? "Production" : "Development";
|
||||
const color: string = EnvConfig.production ? "red" : "green";
|
||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||
const env: string = this.config.production ? 'Production' : 'Development';
|
||||
const color: string = this.config.production ? 'red' : 'green';
|
||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
@@ -48,12 +66,4 @@ export class AppComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
private onInit(): void {
|
||||
if (typeof this._window !== 'undefined') {
|
||||
this.store.dispatch(
|
||||
new HostWindowResizeAction(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { EffectsModule } from "@ngrx/effects";
|
||||
import { HeaderEffects } from "./header/header.effects";
|
||||
import { coreEffects } from "./core/core.effects";
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { HeaderEffects } from './header/header.effects';
|
||||
import { StoreEffects } from './store.effects';
|
||||
import { coreEffects } from './core/core.effects';
|
||||
|
||||
export const effects = [
|
||||
...coreEffects, //TODO should probably be imported in coreModule
|
||||
...coreEffects, // TODO: should probably be imported in coreModule
|
||||
EffectsModule.run(StoreEffects),
|
||||
EffectsModule.run(HeaderEffects)
|
||||
];
|
||||
|
@@ -1,38 +1,63 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
import { RouterStoreModule } from '@ngrx/router-store';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
||||
import { rootReducer, AppState } from './app.reducer';
|
||||
import { effects } from './app.effects';
|
||||
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { HomeModule } from './home/home.module';
|
||||
import { ItemPageModule } from './item-page/item-page.module';
|
||||
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
|
||||
import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module';
|
||||
|
||||
import { HomeModule } from './home/home.module';
|
||||
import { ItemPageModule } from './item-page/item-page.module';
|
||||
import { CollectionPageModule } from './collection-page/collection-page.module';
|
||||
import { CommunityPageModule } from './community-page/community-page.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
|
||||
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
|
||||
|
||||
export function getConfig() {
|
||||
return ENV_CONFIG;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
FormsModule,
|
||||
CoreModule.forRoot(),
|
||||
HttpModule,
|
||||
TransferHttpModule,
|
||||
HomeModule,
|
||||
ItemPageModule,
|
||||
CollectionPageModule,
|
||||
CommunityPageModule,
|
||||
AppRoutingModule,
|
||||
StoreModule.provideStore(rootReducer),
|
||||
RouterStoreModule.connectRouter(),
|
||||
StoreDevtoolsModule.instrumentOnlyWithExtension(),
|
||||
effects
|
||||
],
|
||||
providers: [
|
||||
{ provide: GLOBAL_CONFIG, useFactory: (getConfig) },
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
PageNotFoundComponent,
|
||||
],
|
||||
imports: [
|
||||
SharedModule,
|
||||
HomeModule,
|
||||
ItemPageModule,
|
||||
CollectionPageModule,
|
||||
CommunityPageModule,
|
||||
CoreModule.forRoot(),
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [
|
||||
]
|
||||
exports: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
|
||||
export { AppComponent } from './app.component';
|
||||
}
|
||||
|
45
src/app/app.reducer.ts
Normal file
45
src/app/app.reducer.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { combineReducers, ActionReducer } from '@ngrx/store';
|
||||
import { routerReducer, RouterState } from '@ngrx/router-store';
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { compose } from '@ngrx/core';
|
||||
|
||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||
import { CoreState, coreReducer } from './core/core.reducers';
|
||||
|
||||
import { StoreActionTypes } from './store.actions';
|
||||
|
||||
import { ENV_CONFIG } from '../config';
|
||||
|
||||
export interface AppState {
|
||||
core: CoreState;
|
||||
router: RouterState;
|
||||
hostWindow: HostWindowState;
|
||||
header: HeaderState;
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
core: coreReducer,
|
||||
router: routerReducer,
|
||||
hostWindow: hostWindowReducer,
|
||||
header: headerReducer
|
||||
};
|
||||
|
||||
export function rootReducer(state: any, action: any) {
|
||||
switch (action.type) {
|
||||
case StoreActionTypes.REHYDRATE:
|
||||
state = Object.assign({}, state, action.payload);
|
||||
break;
|
||||
case StoreActionTypes.REPLAY:
|
||||
break;
|
||||
default:
|
||||
}
|
||||
let root: ActionReducer<any>;
|
||||
// TODO: attempt to not use InjectionToken GLOBAL_CONFIG over GlobalConfig ENV_CONFIG
|
||||
if (ENV_CONFIG.production) {
|
||||
root = combineReducers(reducers)(state, action);
|
||||
} else {
|
||||
root = compose(storeFreeze, combineReducers)(reducers)(state, action);
|
||||
}
|
||||
return root;
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { routerReducer, RouterState } from "@ngrx/router-store";
|
||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
|
||||
import { CoreState, coreReducer } from "./core/core.reducers";
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { compose } from "@ngrx/core";
|
||||
import { StoreActionTypes } from "./store.actions";
|
||||
|
||||
import { EnvConfig } from '../config';
|
||||
|
||||
export interface AppState {
|
||||
core: CoreState;
|
||||
router: RouterState;
|
||||
hostWindow: HostWindowState;
|
||||
header: HeaderState;
|
||||
}
|
||||
|
||||
export const reducers = {
|
||||
core: coreReducer,
|
||||
router: routerReducer,
|
||||
hostWindow: hostWindowReducer,
|
||||
header: headerReducer
|
||||
};
|
||||
|
||||
export function rootReducer(state: any, action: any) {
|
||||
let output;
|
||||
if (action.type === StoreActionTypes.REHYDRATE) {
|
||||
state = action.payload;
|
||||
}
|
||||
if (EnvConfig.production) {
|
||||
output = combineReducers(reducers)(state, action);
|
||||
} else {
|
||||
output = compose(storeFreeze, combineReducers)(reducers)(state, action);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export const NGRX_CACHE_KEY = "NGRX_STORE";
|
76
src/app/browser-app.module.ts
Normal file
76
src/app/browser-app.module.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
|
||||
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { TransferState } from '../modules/transfer-state/transfer-state';
|
||||
import { BrowserCookiesModule } from '../modules/cookies/browser-cookies.module';
|
||||
import { BrowserDataLoaderModule } from '../modules/data-loader/browser-data-loader.module';
|
||||
import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module';
|
||||
import { BrowserTransferStoreEffects } from '../modules/transfer-store/browser-transfer-store.effects';
|
||||
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
|
||||
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
export function init(cache: TransferState) {
|
||||
return () => {
|
||||
cache.initialize();
|
||||
};
|
||||
}
|
||||
|
||||
export function HttpLoaderFactory(http: Http) {
|
||||
return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({
|
||||
appId: 'ds-app-id'
|
||||
}),
|
||||
IdlePreloadModule.forRoot(), // forRoot ensures the providers are only created once
|
||||
RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [Http]
|
||||
}
|
||||
}),
|
||||
NgbModule.forRoot(),
|
||||
BrowserCookiesModule,
|
||||
BrowserDataLoaderModule,
|
||||
BrowserTransferStateModule,
|
||||
BrowserTransferStoreModule,
|
||||
EffectsModule.run(BrowserTransferStoreEffects),
|
||||
BrowserAnimationsModule,
|
||||
AppModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory: init,
|
||||
deps: [
|
||||
TransferState
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
export class BrowserAppModule {
|
||||
|
||||
}
|
@@ -4,21 +4,22 @@ import {
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { Collection } from "../core/shared/collection.model";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { CollectionDataService } from "../core/data/collection-data.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ItemDataService } from "../core/data/item-data.service";
|
||||
import { Item } from "../core/shared/item.model";
|
||||
import { SortOptions, SortDirection } from "../core/cache/models/sort-options.model";
|
||||
import { PaginationComponentOptions } from "../shared/pagination/pagination-component-options.model";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { hasValue } from "../shared/empty.util";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page',
|
||||
styleUrls: ['./collection-page.component.css'],
|
||||
styleUrls: ['./collection-page.component.scss'],
|
||||
templateUrl: './collection-page.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
@@ -26,8 +27,8 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
collectionData: RemoteData<Collection>;
|
||||
itemData: RemoteData<Item[]>;
|
||||
logoData: RemoteData<Bitstream>;
|
||||
config : PaginationComponentOptions;
|
||||
sortConfig : SortOptions;
|
||||
config: PaginationComponentOptions;
|
||||
sortConfig: SortOptions;
|
||||
private subs: Subscription[] = [];
|
||||
private collectionId: string;
|
||||
|
||||
@@ -37,22 +38,21 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
private ref: ChangeDetectorRef,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subs.push(this.route.params.map((params: Params) => params['id'] )
|
||||
this.subs.push(this.route.params.map((params: Params) => params.id)
|
||||
.subscribe((id: string) => {
|
||||
this.collectionId = id;
|
||||
this.collectionData = this.collectionDataService.findById(this.collectionId);
|
||||
this.subs.push(this.collectionData.payload
|
||||
.subscribe(collection => this.logoData = collection.logo));
|
||||
this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo));
|
||||
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = "collection-browse";
|
||||
this.config.pageSizeOptions = [ 4 ];
|
||||
this.config.id = 'collection-browse';
|
||||
this.config.pageSizeOptions = [4];
|
||||
this.config.pageSize = 4;
|
||||
this.sortConfig = new SortOptions();
|
||||
this.sortConfig = new SortOptions();
|
||||
|
||||
this.updateResults();
|
||||
}));
|
||||
@@ -60,12 +60,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter(sub => hasValue(sub))
|
||||
.forEach(sub => sub.unsubscribe());
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
onPageChange(currentPage: number): void {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
|
@@ -2,17 +2,17 @@
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="(communityData.payload | async)?.name"></ds-comcol-page-header>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoData"
|
||||
<ds-comcol-page-logo *ngIf="logoData"
|
||||
[logo]="logoData.payload | async"
|
||||
[alternateText]="'Community Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Introductionary text -->
|
||||
<ds-comcol-page-content
|
||||
<ds-comcol-page-content
|
||||
[content]="(communityData.payload | async)?.introductoryText"
|
||||
[hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
<!-- News -->
|
||||
<ds-comcol-page-content
|
||||
<ds-comcol-page-content
|
||||
[content]="(communityData.payload | async)?.sidebarText"
|
||||
[hasInnerHtml]="true"
|
||||
[title]="'community.page.news'">
|
||||
@@ -21,6 +21,6 @@
|
||||
<ds-comcol-page-content
|
||||
[content]="(communityData.payload | async)?.copyrightText"
|
||||
[hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
</ds-comcol-page-content>
|
||||
<ds-community-page-sub-collection-list></ds-community-page-sub-collection-list>
|
||||
</div>
|
||||
|
@@ -1,16 +1,17 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { Community } from "../core/shared/community.model";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { CommunityDataService } from "../core/data/community-data.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { hasValue } from "../shared/empty.util";
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page',
|
||||
styleUrls: ['./community-page.component.css'],
|
||||
styleUrls: ['./community-page.component.scss'],
|
||||
templateUrl: './community-page.component.html',
|
||||
})
|
||||
export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||
@@ -22,23 +23,21 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||
private communityDataService: CommunityDataService,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params.subscribe((params: Params) => {
|
||||
this.communityData = this.communityDataService.findById(params['id']);
|
||||
this.communityData = this.communityDataService.findById(params.id);
|
||||
this.subs.push(this.communityData.payload
|
||||
.subscribe(community => this.logoData = community.logo));
|
||||
.subscribe((community) => this.logoData = community.logo));
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter(sub => hasValue(sub))
|
||||
.forEach(sub => sub.unsubscribe());
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CommunityPageComponent } from './community-page.component';
|
||||
|
@@ -1 +1 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/variables.scss';
|
||||
|
@@ -1,28 +1,22 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CollectionDataService } from "../../core/data/collection-data.service";
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { Collection } from "../../core/shared/collection.model";
|
||||
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page-sub-collection-list',
|
||||
styleUrls: ['./community-page-sub-collection-list.component.css'],
|
||||
templateUrl: './community-page-sub-collection-list.component.html',
|
||||
selector: 'ds-community-page-sub-collection-list',
|
||||
styleUrls: ['./community-page-sub-collection-list.component.scss'],
|
||||
templateUrl: './community-page-sub-collection-list.component.html',
|
||||
})
|
||||
export class CommunityPageSubCollectionListComponent implements OnInit {
|
||||
subCollections: RemoteData<Collection[]>;
|
||||
subCollections: RemoteData<Collection[]>;
|
||||
|
||||
constructor(
|
||||
private cds: CollectionDataService
|
||||
) {
|
||||
this.universalInit();
|
||||
}
|
||||
constructor(private cds: CollectionDataService) {
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subCollections = this.cds.findAll();
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.subCollections = this.cds.findAll();
|
||||
}
|
||||
}
|
||||
|
37
src/app/core/cache/builders/build-decorators.ts
vendored
37
src/app/core/cache/builders/build-decorators.ts
vendored
@@ -1,28 +1,29 @@
|
||||
import "reflect-metadata";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import 'reflect-metadata';
|
||||
|
||||
const mapsToMetadataKey = Symbol("mapsTo");
|
||||
const relationshipKey = Symbol("relationship");
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
const mapsToMetadataKey = Symbol('mapsTo');
|
||||
const relationshipKey = Symbol('relationship');
|
||||
|
||||
const relationshipMap = new Map();
|
||||
|
||||
export const mapsTo = function(value: GenericConstructor<CacheableObject>) {
|
||||
export function mapsTo(value: GenericConstructor<CacheableObject>) {
|
||||
return Reflect.metadata(mapsToMetadataKey, value);
|
||||
};
|
||||
}
|
||||
|
||||
export const getMapsTo = function(target: any) {
|
||||
export function getMapsTo(target: any) {
|
||||
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
|
||||
};
|
||||
}
|
||||
|
||||
export const relationship = function(value: ResourceType, isList: boolean = false): any {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
export function relationship(value: ResourceType, isList: boolean = false): any {
|
||||
return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (!target || !propertyKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metaDataList : Array<string> = relationshipMap.get(target.constructor) || [];
|
||||
const metaDataList: string[] = relationshipMap.get(target.constructor) || [];
|
||||
if (metaDataList.indexOf(propertyKey) === -1) {
|
||||
metaDataList.push(propertyKey);
|
||||
}
|
||||
@@ -30,12 +31,12 @@ export const relationship = function(value: ResourceType, isList: boolean = fals
|
||||
|
||||
return Reflect.metadata(relationshipKey, { resourceType: value, isList }).apply(this, arguments);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const getRelationMetadata = function(target: any, propertyKey: string) {
|
||||
export function getRelationMetadata(target: any, propertyKey: string) {
|
||||
return Reflect.getMetadata(relationshipKey, target, propertyKey);
|
||||
};
|
||||
}
|
||||
|
||||
export const getRelationships = function(target: any) {
|
||||
export function getRelationships(target: any) {
|
||||
return relationshipMap.get(target);
|
||||
};
|
||||
}
|
||||
|
@@ -1,20 +1,21 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { ObjectCacheService } from "../object-cache.service";
|
||||
import { RequestService } from "../../data/request.service";
|
||||
import { ResponseCacheService } from "../response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { CoreState } from "../../core.reducers";
|
||||
import { RequestEntry } from "../../data/request.reducer";
|
||||
import { hasValue, isNotEmpty } from "../../../shared/empty.util";
|
||||
import { ResponseCacheEntry } from "../response-cache.reducer";
|
||||
import { ErrorResponse, SuccessResponse } from "../response-cache.models";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { RemoteData } from "../../data/remote-data";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { getMapsTo, getRelationMetadata, getRelationships } from "./build-decorators";
|
||||
import { NormalizedObjectFactory } from "../models/normalized-object-factory";
|
||||
import { Request } from "../../data/request.models";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { ObjectCacheService } from '../object-cache.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { ResponseCacheService } from '../response-cache.service';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ResponseCacheEntry } from '../response-cache.reducer';
|
||||
import { ErrorResponse, SuccessResponse } from '../response-cache.models';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
|
||||
import { NormalizedObjectFactory } from '../models/normalized-object-factory';
|
||||
import { Request } from '../../data/request.models';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteDataBuildService {
|
||||
@@ -33,14 +34,14 @@ export class RemoteDataBuildService {
|
||||
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
|
||||
|
||||
const requestObs = Observable.race(
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', href).filter(entry => hasValue(entry)),
|
||||
requestHrefObs.flatMap(requestHref =>
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', requestHref)).filter(entry => hasValue(entry))
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', href).filter((entry) => hasValue(entry)),
|
||||
requestHrefObs.flatMap((requestHref) =>
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
const responseCacheObs = Observable.race(
|
||||
this.responseCache.get(href).filter(entry => hasValue(entry)),
|
||||
requestHrefObs.flatMap(requestHref => this.responseCache.get(requestHref)).filter(entry => hasValue(entry))
|
||||
this.responseCache.get(href).filter((entry) => hasValue(entry)),
|
||||
requestHrefObs.flatMap((requestHref) => this.responseCache.get(requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
|
||||
@@ -52,30 +53,31 @@ export class RemoteDataBuildService {
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
//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 =
|
||||
Observable.combineLatest(
|
||||
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType).startWith(undefined),
|
||||
responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
if (isNotEmpty(resourceUUIDs)) {
|
||||
return this.objectCache.get(resourceUUIDs[0], normalizedType);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return Observable.of(undefined);
|
||||
}
|
||||
})
|
||||
@@ -84,16 +86,14 @@ export class RemoteDataBuildService {
|
||||
(fromSelfLink, fromResponse) => {
|
||||
if (hasValue(fromSelfLink)) {
|
||||
return fromSelfLink;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return fromResponse;
|
||||
}
|
||||
}
|
||||
).filter(normalized => hasValue(normalized))
|
||||
).filter((normalized) => hasValue(normalized))
|
||||
.map((normalized: TNormalized) => {
|
||||
return this.build<TNormalized, TDomain>(normalized);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return new RemoteData(
|
||||
href,
|
||||
@@ -112,8 +112,8 @@ export class RemoteDataBuildService {
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain[]> {
|
||||
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href)
|
||||
.filter(entry => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter(entry => hasValue(entry));
|
||||
.filter((entry) => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry));
|
||||
|
||||
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
|
||||
|
||||
@@ -124,22 +124,24 @@ export class RemoteDataBuildService {
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
const payload = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
return this.objectCache.getList(resourceUUIDs, normalizedType)
|
||||
.map((normList: TNormalized[]) => {
|
||||
return normList.map((normalized: TNormalized) => {
|
||||
@@ -161,9 +163,8 @@ export class RemoteDataBuildService {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
build<TNormalized extends CacheableObject, TDomain>(normalized: TNormalized): TDomain {
|
||||
let links: any = {};
|
||||
const links: any = {};
|
||||
|
||||
const relationships = getRelationships(normalized.constructor) || [];
|
||||
|
||||
@@ -180,24 +181,22 @@ export class RemoteDataBuildService {
|
||||
});
|
||||
}, 0);
|
||||
|
||||
let rdArr = [];
|
||||
const rdArr = [];
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
rdArr.push(this.buildSingle(href, resourceConstructor));
|
||||
});
|
||||
|
||||
if (rdArr.length === 1) {
|
||||
links[relationship] = rdArr[0];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
links[relationship] = this.aggregate(rdArr);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(new Request(normalized[relationship]));
|
||||
},0);
|
||||
}, 0);
|
||||
|
||||
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
||||
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
|
||||
@@ -215,51 +214,49 @@ export class RemoteDataBuildService {
|
||||
return Object.assign(new domainModel(), normalized, links);
|
||||
}
|
||||
|
||||
aggregate<T>(input: RemoteData<T>[]): RemoteData<T[]> {
|
||||
aggregate<T>(input: Array<RemoteData<T>>): RemoteData<T[]> {
|
||||
const requestPending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
...input.map((rd) => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const responsePending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
...input.map((rd) => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const isSuccessFul = Observable.combineLatest(
|
||||
...input.map(rd => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every(e => e === true))
|
||||
...input.map((rd) => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const errorMessage = Observable.combineLatest(
|
||||
...input.map(rd => rd.errorMessage),
|
||||
...input.map((rd) => rd.errorMessage),
|
||||
).map((...errors) => errors
|
||||
.map((e, idx) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e}`;
|
||||
}
|
||||
})
|
||||
.filter(e => hasValue(e))
|
||||
.join(", ")
|
||||
);
|
||||
.filter((e) => hasValue(e))
|
||||
.join(', ')
|
||||
);
|
||||
|
||||
const statusCode = Observable.combineLatest(
|
||||
...input.map(rd => rd.statusCode),
|
||||
...input.map((rd) => rd.statusCode),
|
||||
).map((...statusCodes) => statusCodes
|
||||
.map((code, idx) => {
|
||||
if (hasValue(code)) {
|
||||
return `[${idx}]: ${code}`;
|
||||
}
|
||||
})
|
||||
.filter(c => hasValue(c))
|
||||
.join(", ")
|
||||
);
|
||||
.filter((c) => hasValue(c))
|
||||
.join(', ')
|
||||
);
|
||||
|
||||
const pageInfo = Observable.of(undefined);
|
||||
|
||||
const payload = <Observable<T[]>> Observable.combineLatest(
|
||||
...input.map(rd => rd.payload)
|
||||
);
|
||||
const payload = Observable.combineLatest(...input.map((rd) => rd.payload)) as Observable<T[]>;
|
||||
|
||||
return new RemoteData(
|
||||
// This is an aggregated object, it doesn't necessarily correspond
|
||||
@@ -275,4 +272,5 @@ export class RemoteDataBuildService {
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
7
src/app/core/cache/cache.reducers.ts
vendored
7
src/app/core/cache/cache.reducers.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { ResponseCacheState, responseCacheReducer } from "./response-cache.reducer";
|
||||
import { ObjectCacheState, objectCacheReducer } from "./object-cache.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { ResponseCacheState, responseCacheReducer } from './response-cache.reducer';
|
||||
import { ObjectCacheState, objectCacheReducer } from './object-cache.reducer';
|
||||
|
||||
export interface CacheState {
|
||||
response: ResponseCacheState,
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { inheritSerialization } from "cerialize";
|
||||
import { inheritSerialization } from 'cerialize';
|
||||
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
|
||||
@inheritSerialization(NormalizedObject)
|
||||
export class NormalizedBitstreamFormat extends NormalizedObject {
|
||||
//TODO this class was created as a placeholder when we connected to the live rest api
|
||||
// TODO: this class was created as a placeholder when we connected to the live rest api
|
||||
|
||||
get uuid(): string {
|
||||
return this.self;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { inheritSerialization, autoserialize } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Bitstream } from "../../shared/bitstream.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { inheritSerialization, autoserialize } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Bitstream } from '../../shared/bitstream.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Bitstream)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -43,7 +44,7 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
@@ -57,4 +58,5 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
bundleName: string;
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Bundle } from "../../shared/bundle.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Bundle } from '../../shared/bundle.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Bundle)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -17,7 +18,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bundle
|
||||
*/
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Item that owns this Bundle
|
||||
@@ -26,5 +27,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Bitstream, true)
|
||||
bitstreams: Array<string>;
|
||||
bitstreams: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Collection } from '../../shared/collection.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Collection)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -26,7 +27,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Community, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Community that owns this Collection
|
||||
@@ -37,6 +38,6 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
items: Array<string>;
|
||||
items: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Community } from "../../shared/community.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Community } from '../../shared/community.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Community)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -26,7 +27,7 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Community, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Community that owns this Community
|
||||
@@ -37,6 +38,6 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
collections: Array<string>;
|
||||
collections: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from "cerialize";
|
||||
import { Metadatum } from "../../shared/metadatum.model";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { Metadatum } from '../../shared/metadatum.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export abstract class NormalizedDSpaceObject extends NormalizedObject{
|
||||
export abstract class NormalizedDSpaceObject extends NormalizedObject {
|
||||
|
||||
/**
|
||||
* The link to the rest endpoint where this object can be found
|
||||
@@ -51,17 +52,18 @@ export abstract class NormalizedDSpaceObject extends NormalizedObject{
|
||||
* An array containing all metadata of this DSpaceObject
|
||||
*/
|
||||
@autoserializeAs(Metadatum)
|
||||
metadata: Array<Metadatum>;
|
||||
metadata: Metadatum[];
|
||||
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
@autoserialize
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
*/
|
||||
@autoserialize
|
||||
owner: string;
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { inheritSerialization, autoserialize, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Item } from "../../shared/item.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { inheritSerialization, autoserialize, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Item } from '../../shared/item.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Item)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -43,7 +44,7 @@ export class NormalizedItem extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
@@ -53,5 +54,6 @@ export class NormalizedItem extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Bitstream, true)
|
||||
bitstreams: Array<string>;
|
||||
bitstreams: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { NormalizedBitstream } from "./normalized-bitstream.model";
|
||||
import { NormalizedBundle } from "./normalized-bundle.model";
|
||||
import { NormalizedItem } from "./normalized-item.model";
|
||||
import { NormalizedCollection } from "./normalized-collection.model";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { NormalizedCommunity } from "./normalized-community.model";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { NormalizedBitstreamFormat } from "./normalized-bitstream-format.model";
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { NormalizedBitstream } from './normalized-bitstream.model';
|
||||
import { NormalizedBundle } from './normalized-bundle.model';
|
||||
import { NormalizedItem } from './normalized-item.model';
|
||||
import { NormalizedCollection } from './normalized-collection.model';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { NormalizedCommunity } from './normalized-community.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
|
||||
|
||||
export class NormalizedObjectFactory {
|
||||
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { autoserialize } from "cerialize";
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { autoserialize } from 'cerialize';
|
||||
/**
|
||||
* An abstract model class for a NormalizedObject.
|
||||
*/
|
||||
|
3
src/app/core/cache/models/self-link.model.ts
vendored
3
src/app/core/cache/models/self-link.model.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { autoserialize } from "cerialize";
|
||||
import { autoserialize } from 'cerialize';
|
||||
|
||||
export class SelfLink {
|
||||
|
||||
@@ -7,4 +7,5 @@ export class SelfLink {
|
||||
|
||||
@autoserialize
|
||||
uuid: string;
|
||||
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ export enum SortDirection {
|
||||
|
||||
export class SortOptions {
|
||||
|
||||
constructor (public field: string = "name", public direction : SortDirection = SortDirection.Ascending) {
|
||||
constructor(public field: string = 'name', public direction: SortDirection = SortDirection.Ascending) {
|
||||
|
||||
}
|
||||
}
|
||||
|
9
src/app/core/cache/object-cache.actions.ts
vendored
9
src/app/core/cache/object-cache.actions.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { CacheableObject } from "./object-cache.reducer";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { CacheableObject } from './object-cache.reducer';
|
||||
|
||||
/**
|
||||
* The list of ObjectCacheAction type definitions
|
||||
@@ -11,6 +12,7 @@ export const ObjectCacheActionTypes = {
|
||||
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
* An ngrx action to add an object to the cache
|
||||
*/
|
||||
@@ -77,6 +79,7 @@ export class ResetObjectCacheTimestampsAction implements Action {
|
||||
this.payload = newTimestamp;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all ObjectCacheActions
|
||||
|
55
src/app/core/cache/object-cache.reducer.spec.ts
vendored
55
src/app/core/cache/object-cache.reducer.spec.ts
vendored
@@ -1,9 +1,10 @@
|
||||
import * as deepFreeze from "deep-freeze";
|
||||
import { objectCacheReducer } from "./object-cache.reducer";
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { objectCacheReducer } from './object-cache.reducer';
|
||||
import {
|
||||
AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
||||
} from "./object-cache.actions";
|
||||
} from './object-cache.actions';
|
||||
|
||||
class NullAction extends RemoveFromObjectCacheAction {
|
||||
type = null;
|
||||
@@ -14,51 +15,51 @@ class NullAction extends RemoveFromObjectCacheAction {
|
||||
}
|
||||
}
|
||||
|
||||
describe("objectCacheReducer", () => {
|
||||
describe('objectCacheReducer', () => {
|
||||
const uuid1 = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||
const uuid2 = '28b04544-1766-4e82-9728-c4e93544ecd3';
|
||||
const testState = {
|
||||
[uuid1]: {
|
||||
data: {
|
||||
uuid: uuid1,
|
||||
foo: "bar"
|
||||
foo: 'bar'
|
||||
},
|
||||
timeAdded: new Date().getTime(),
|
||||
msToLive: 900000,
|
||||
requestHref: "https://rest.api/endpoint/uuid1"
|
||||
requestHref: 'https://rest.api/endpoint/uuid1'
|
||||
},
|
||||
[uuid2]: {
|
||||
data: {
|
||||
uuid: uuid2,
|
||||
foo: "baz"
|
||||
foo: 'baz'
|
||||
},
|
||||
timeAdded: new Date().getTime(),
|
||||
msToLive: 900000,
|
||||
requestHref: "https://rest.api/endpoint/uuid2"
|
||||
requestHref: 'https://rest.api/endpoint/uuid2'
|
||||
}
|
||||
};
|
||||
deepFreeze(testState);
|
||||
|
||||
it("should return the current state when no valid actions have been made", () => {
|
||||
it('should return the current state when no valid actions have been made', () => {
|
||||
const action = new NullAction();
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
|
||||
expect(newState).toEqual(testState);
|
||||
});
|
||||
|
||||
it("should start with an empty cache", () => {
|
||||
it('should start with an empty cache', () => {
|
||||
const action = new NullAction();
|
||||
const initialState = objectCacheReducer(undefined, action);
|
||||
|
||||
expect(initialState).toEqual(Object.create(null));
|
||||
});
|
||||
|
||||
it("should add the payload to the cache in response to an ADD action", () => {
|
||||
it('should add the payload to the cache in response to an ADD action', () => {
|
||||
const state = Object.create(null);
|
||||
const objectToCache = {uuid: uuid1};
|
||||
const objectToCache = { uuid: uuid1 };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
const requestHref = 'https://rest.api/endpoint/uuid1';
|
||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
|
||||
const newState = objectCacheReducer(state, action);
|
||||
|
||||
@@ -67,31 +68,33 @@ describe("objectCacheReducer", () => {
|
||||
expect(newState[uuid1].msToLive).toEqual(msToLive);
|
||||
});
|
||||
|
||||
it("should overwrite an object in the cache in response to an ADD action if it already exists", () => {
|
||||
const objectToCache = {uuid: uuid1, foo: "baz", somethingElse: true};
|
||||
it('should overwrite an object in the cache in response to an ADD action if it already exists', () => {
|
||||
const objectToCache = { uuid: uuid1, foo: 'baz', somethingElse: true };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
const requestHref = 'https://rest.api/endpoint/uuid1';
|
||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
|
||||
expect(newState[uuid1].data['foo']).toBe("baz");
|
||||
/* tslint:disable:no-string-literal */
|
||||
expect(newState[uuid1].data['foo']).toBe('baz');
|
||||
expect(newState[uuid1].data['somethingElse']).toBe(true);
|
||||
/* tslint:enable:no-string-literal */
|
||||
});
|
||||
|
||||
it("should perform the ADD action without affecting the previous state", () => {
|
||||
it('should perform the ADD action without affecting the previous state', () => {
|
||||
const state = Object.create(null);
|
||||
const objectToCache = {uuid: uuid1};
|
||||
const objectToCache = { uuid: uuid1 };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
const requestHref = 'https://rest.api/endpoint/uuid1';
|
||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
|
||||
deepFreeze(state);
|
||||
|
||||
objectCacheReducer(state, action);
|
||||
});
|
||||
|
||||
it("should remove the specified object from the cache in response to the REMOVE action", () => {
|
||||
it('should remove the specified object from the cache in response to the REMOVE action', () => {
|
||||
const action = new RemoveFromObjectCacheAction(uuid1);
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
|
||||
@@ -108,13 +111,13 @@ describe("objectCacheReducer", () => {
|
||||
expect(newState).toEqual(testState);
|
||||
});
|
||||
|
||||
it("should perform the REMOVE action without affecting the previous state", () => {
|
||||
it('should perform the REMOVE action without affecting the previous state', () => {
|
||||
const action = new RemoveFromObjectCacheAction(uuid1);
|
||||
//testState has already been frozen above
|
||||
// testState has already been frozen above
|
||||
objectCacheReducer(testState, action);
|
||||
});
|
||||
|
||||
it("should set the timestamp of all objects in the cache in response to a RESET_TIMESTAMPS action", () => {
|
||||
it('should set the timestamp of all objects in the cache in response to a RESET_TIMESTAMPS action', () => {
|
||||
const newTimestamp = new Date().getTime();
|
||||
const action = new ResetObjectCacheTimestampsAction(newTimestamp);
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
@@ -123,9 +126,9 @@ describe("objectCacheReducer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should perform the RESET_TIMESTAMPS action without affecting the previous state", () => {
|
||||
it('should perform the RESET_TIMESTAMPS action without affecting the previous state', () => {
|
||||
const action = new ResetObjectCacheTimestampsAction(new Date().getTime());
|
||||
//testState has already been frozen above
|
||||
// testState has already been frozen above
|
||||
objectCacheReducer(testState, action);
|
||||
});
|
||||
|
||||
|
21
src/app/core/cache/object-cache.reducer.ts
vendored
21
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
||||
} from "./object-cache.actions";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { CacheEntry } from "./cache-entry";
|
||||
} from './object-cache.actions';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { CacheEntry } from './cache-entry';
|
||||
|
||||
/**
|
||||
* An interface to represent objects that can be cached
|
||||
@@ -52,15 +52,15 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
|
||||
switch (action.type) {
|
||||
|
||||
case ObjectCacheActionTypes.ADD: {
|
||||
return addToObjectCache(state, <AddToObjectCacheAction>action);
|
||||
return addToObjectCache(state, action as AddToObjectCacheAction);
|
||||
}
|
||||
|
||||
case ObjectCacheActionTypes.REMOVE: {
|
||||
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
|
||||
return removeFromObjectCache(state, action as RemoveFromObjectCacheAction)
|
||||
}
|
||||
|
||||
case ObjectCacheActionTypes.RESET_TIMESTAMPS: {
|
||||
return resetObjectCacheTimestamps(state, <ResetObjectCacheTimestampsAction>action)
|
||||
return resetObjectCacheTimestamps(state, action as ResetObjectCacheTimestampsAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -102,12 +102,11 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio
|
||||
*/
|
||||
function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObjectCacheAction): ObjectCacheState {
|
||||
if (hasValue(state[action.payload])) {
|
||||
let newObjectCache = Object.assign({}, state);
|
||||
const newObjectCache = Object.assign({}, state);
|
||||
delete newObjectCache[action.payload];
|
||||
|
||||
return newObjectCache;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -123,8 +122,8 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject
|
||||
* the new state, with all timeAdded timestamps set to the specified value
|
||||
*/
|
||||
function resetObjectCacheTimestamps(state: ObjectCacheState, action: ResetObjectCacheTimestampsAction): ObjectCacheState {
|
||||
let newState = Object.create(null);
|
||||
Object.keys(state).forEach(key => {
|
||||
const newState = Object.create(null);
|
||||
Object.keys(state).forEach((key) => {
|
||||
newState[key] = Object.assign({}, state[key], {
|
||||
timeAdded: action.payload
|
||||
});
|
||||
|
72
src/app/core/cache/object-cache.service.spec.ts
vendored
72
src/app/core/cache/object-cache.service.spec.ts
vendored
@@ -1,21 +1,22 @@
|
||||
import { ObjectCacheState, CacheableObject } from "./object-cache.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { ObjectCacheService } from "./object-cache.service";
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||
import { Observable } from "rxjs";
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ObjectCacheService } from './object-cache.service';
|
||||
import { ObjectCacheState, CacheableObject } from './object-cache.reducer';
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
|
||||
class TestClass implements CacheableObject {
|
||||
constructor(
|
||||
public uuid: string,
|
||||
public foo: string
|
||||
) {}
|
||||
) { }
|
||||
|
||||
test(): string {
|
||||
return this.foo + this.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
describe("ObjectCacheService", () => {
|
||||
describe('ObjectCacheService', () => {
|
||||
let service: ObjectCacheService;
|
||||
let store: Store<ObjectCacheState>;
|
||||
|
||||
@@ -39,76 +40,81 @@ describe("ObjectCacheService", () => {
|
||||
spyOn(store, 'dispatch');
|
||||
service = new ObjectCacheService(store);
|
||||
|
||||
spyOn(window, 'Date').and.returnValue({ getTime: () => timestamp });
|
||||
spyOn(Date.prototype, 'getTime').and.callFake(() => {
|
||||
return timestamp;
|
||||
});
|
||||
});
|
||||
|
||||
describe("add", () => {
|
||||
it("should dispatch an ADD action with the object to add, the time to live, and the current timestamp", () => {
|
||||
|
||||
describe('add', () => {
|
||||
it('should dispatch an ADD action with the object to add, the time to live, and the current timestamp', () => {
|
||||
service.add(objectToCache, msToLive, requestHref);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new AddToObjectCacheAction(objectToCache, timestamp, msToLive, requestHref));
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should dispatch a REMOVE action with the UUID of the object to remove", () => {
|
||||
describe('remove', () => {
|
||||
it('should dispatch a REMOVE action with the UUID of the object to remove', () => {
|
||||
service.remove(uuid);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new RemoveFromObjectCacheAction(uuid));
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("should return an observable of the cached object with the specified UUID and type", () => {
|
||||
describe('get', () => {
|
||||
it('should return an observable of the cached object with the specified UUID and type', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
|
||||
|
||||
let testObj: any;
|
||||
//due to the implementation of spyOn above, this subscribe will be synchronous
|
||||
service.get(uuid, TestClass).take(1).subscribe(o => testObj = o);
|
||||
// due to the implementation of spyOn above, this subscribe will be synchronous
|
||||
service.get(uuid, TestClass).take(1).subscribe((o) => testObj = o);
|
||||
expect(testObj.uuid).toBe(uuid);
|
||||
expect(testObj.foo).toBe("bar");
|
||||
expect(testObj.foo).toBe('bar');
|
||||
// this only works if testObj is an instance of TestClass
|
||||
expect(testObj.test()).toBe("bar" + uuid);
|
||||
expect(testObj.test()).toBe('bar' + uuid);
|
||||
});
|
||||
|
||||
it("should not return a cached object that has exceeded its time to live", () => {
|
||||
it('should not return a cached object that has exceeded its time to live', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
||||
|
||||
let getObsHasFired = false;
|
||||
const subscription = service.get(uuid, TestClass).subscribe(o => getObsHasFired = true);
|
||||
const subscription = service.get(uuid, TestClass).subscribe((o) => getObsHasFired = true);
|
||||
expect(getObsHasFired).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getList", () => {
|
||||
it("should return an observable of the array of cached objects with the specified UUID and type", () => {
|
||||
spyOn(service, 'get').and.returnValue(Observable.of(new TestClass(uuid, "bar")));
|
||||
describe('getList', () => {
|
||||
it('should return an observable of the array of cached objects with the specified UUID and type', () => {
|
||||
spyOn(service, 'get').and.returnValue(Observable.of(new TestClass(uuid, 'bar')));
|
||||
|
||||
let testObjs: Array<any>;
|
||||
service.getList([uuid, uuid], TestClass).take(1).subscribe(arr => testObjs = arr);
|
||||
let testObjs: any[];
|
||||
service.getList([uuid, uuid], TestClass).take(1).subscribe((arr) => testObjs = arr);
|
||||
expect(testObjs[0].uuid).toBe(uuid);
|
||||
expect(testObjs[0].foo).toBe("bar");
|
||||
expect(testObjs[0].test()).toBe("bar" + uuid);
|
||||
expect(testObjs[0].foo).toBe('bar');
|
||||
expect(testObjs[0].test()).toBe('bar' + uuid);
|
||||
expect(testObjs[1].uuid).toBe(uuid);
|
||||
expect(testObjs[1].foo).toBe("bar");
|
||||
expect(testObjs[1].test()).toBe("bar" + uuid);
|
||||
expect(testObjs[1].foo).toBe('bar');
|
||||
expect(testObjs[1].test()).toBe('bar' + uuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
it("should return true if the object with the supplied UUID is cached and still valid", () => {
|
||||
describe('has', () => {
|
||||
it('should return true if the object with the supplied UUID is cached and still valid', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
|
||||
|
||||
expect(service.has(uuid)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if the object with the supplied UUID isn't cached", () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(undefined));
|
||||
|
||||
expect(service.has(uuid)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if the object with the supplied UUID is cached but has exceeded its time to live", () => {
|
||||
it('should return false if the object with the supplied UUID is cached but has exceeded its time to live', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
||||
|
||||
expect(service.has(uuid)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
31
src/app/core/cache/object-cache.service.ts
vendored
31
src/app/core/cache/object-cache.service.ts
vendored
@@ -1,10 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from "./object-cache.reducer";
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||
import { Observable } from "rxjs";
|
||||
import { hasNoValue } from "../../shared/empty.util";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from './object-cache.reducer';
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* A service to interact with the object cache
|
||||
@@ -13,7 +15,7 @@ import { GenericConstructor } from "../shared/generic-constructor";
|
||||
export class ObjectCacheService {
|
||||
constructor(
|
||||
private store: Store<ObjectCacheState>
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Add an object to the cache
|
||||
@@ -59,7 +61,7 @@ export class ObjectCacheService {
|
||||
*/
|
||||
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
|
||||
return this.getEntry(uuid)
|
||||
.map((entry: ObjectCacheEntry) => <T> Object.assign(new type(), entry.data));
|
||||
.map((entry: ObjectCacheEntry) => Object.assign(new type(), entry.data) as T);
|
||||
}
|
||||
|
||||
getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> {
|
||||
@@ -69,7 +71,7 @@ export class ObjectCacheService {
|
||||
|
||||
private getEntry(uuid: string): Observable<ObjectCacheEntry> {
|
||||
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||
.filter(entry => this.isValid(entry))
|
||||
.filter((entry) => this.isValid(entry))
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ export class ObjectCacheService {
|
||||
* The type of the objects to get
|
||||
* @return Observable<Array<T>>
|
||||
*/
|
||||
getList<T extends CacheableObject>(uuids: Array<string>, type: GenericConstructor<T>): Observable<Array<T>> {
|
||||
getList<T extends CacheableObject>(uuids: string[], type: GenericConstructor<T>): Observable<T[]> {
|
||||
return Observable.combineLatest(
|
||||
uuids.map((id: string) => this.get<T>(id, type))
|
||||
);
|
||||
@@ -123,7 +125,7 @@ export class ObjectCacheService {
|
||||
|
||||
this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||
.take(1)
|
||||
.subscribe(entry => result = this.isValid(entry));
|
||||
.subscribe((entry) => result = this.isValid(entry));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -138,7 +140,7 @@ export class ObjectCacheService {
|
||||
* false otherwise
|
||||
*/
|
||||
hasBySelfLink(href: string): boolean {
|
||||
let result: boolean = false;
|
||||
let result = false;
|
||||
|
||||
this.store.select<string>('core', 'index', 'href', href)
|
||||
.take(1)
|
||||
@@ -159,8 +161,7 @@ export class ObjectCacheService {
|
||||
private isValid(entry: ObjectCacheEntry): boolean {
|
||||
if (hasNoValue(entry)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const timeOutdated = entry.timeAdded + entry.msToLive;
|
||||
const isOutDated = new Date().getTime() > timeOutdated;
|
||||
if (isOutDated) {
|
||||
|
9
src/app/core/cache/response-cache.actions.ts
vendored
9
src/app/core/cache/response-cache.actions.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { Response } from "./response-cache.models";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* The list of ResponseCacheAction type definitions
|
||||
@@ -11,6 +12,7 @@ export const ResponseCacheActionTypes = {
|
||||
RESET_TIMESTAMPS: type('dspace/core/cache/response/RESET_TIMESTAMPS')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class ResponseCacheAddAction implements Action {
|
||||
type = ResponseCacheActionTypes.ADD;
|
||||
payload: {
|
||||
@@ -59,6 +61,7 @@ export class ResetResponseCacheTimestampsAction implements Action {
|
||||
this.payload = newTimestamp;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all ResponseCacheActions
|
||||
|
11
src/app/core/cache/response-cache.models.ts
vendored
11
src/app/core/cache/response-cache.models.ts
vendored
@@ -1,16 +1,17 @@
|
||||
import { RequestError } from "../data/request.models";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { RequestError } from '../data/request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Response {
|
||||
constructor(
|
||||
public isSuccessful: boolean,
|
||||
public statusCode: string
|
||||
) {}
|
||||
) { }
|
||||
}
|
||||
|
||||
export class SuccessResponse extends Response {
|
||||
constructor(
|
||||
public resourceUUIDs: Array<String>,
|
||||
public resourceUUIDs: string[],
|
||||
public statusCode: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
@@ -27,4 +28,4 @@ export class ErrorResponse extends Response {
|
||||
this.errorMessage = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
112
src/app/core/cache/response-cache.reducer.spec.ts
vendored
112
src/app/core/cache/response-cache.reducer.spec.ts
vendored
@@ -1,9 +1,11 @@
|
||||
import { responseCacheReducer, ResponseCacheState } from "./response-cache.reducer";
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { responseCacheReducer, ResponseCacheState } from './response-cache.reducer';
|
||||
|
||||
import {
|
||||
ResponseCacheRemoveAction,
|
||||
ResetResponseCacheTimestampsAction
|
||||
} from "./response-cache.actions";
|
||||
import deepFreeze = require("deep-freeze");
|
||||
} from './response-cache.actions';
|
||||
|
||||
class NullAction extends ResponseCacheRemoveAction {
|
||||
type = null;
|
||||
@@ -14,37 +16,37 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
}
|
||||
}
|
||||
|
||||
// describe("responseCacheReducer", () => {
|
||||
// const keys = ["125c17f89046283c5f0640722aac9feb", "a06c3006a41caec5d635af099b0c780c"];
|
||||
// describe('responseCacheReducer', () => {
|
||||
// const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c'];
|
||||
// const services = [new OpaqueToken('service1'), new OpaqueToken('service2')];
|
||||
// const msToLive = 900000;
|
||||
// const uuids = [
|
||||
// "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
// "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
// "be8325f7-243b-49f4-8a4b-df2b793ff3b5"
|
||||
// '9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
// '598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
// 'be8325f7-243b-49f4-8a4b-df2b793ff3b5'
|
||||
// ];
|
||||
// const resourceID = "9978";
|
||||
// const paginationOptions = { "resultsPerPage": 10, "currentPage": 1 };
|
||||
// const sortOptions = { "field": "id", "direction": 0 };
|
||||
// const resourceID = '9978';
|
||||
// const paginationOptions = { 'resultsPerPage': 10, 'currentPage': 1 };
|
||||
// const sortOptions = { 'field': 'id', 'direction': 0 };
|
||||
// const testState = {
|
||||
// [keys[0]]: {
|
||||
// "key": keys[0],
|
||||
// "service": services[0],
|
||||
// "resourceUUIDs": [uuids[0], uuids[1]],
|
||||
// "isLoading": false,
|
||||
// "paginationOptions": paginationOptions,
|
||||
// "sortOptions": sortOptions,
|
||||
// "timeAdded": new Date().getTime(),
|
||||
// "msToLive": msToLive
|
||||
// 'key': keys[0],
|
||||
// 'service': services[0],
|
||||
// 'resourceUUIDs': [uuids[0], uuids[1]],
|
||||
// 'isLoading': false,
|
||||
// 'paginationOptions': paginationOptions,
|
||||
// 'sortOptions': sortOptions,
|
||||
// 'timeAdded': new Date().getTime(),
|
||||
// 'msToLive': msToLive
|
||||
// },
|
||||
// [keys[1]]: {
|
||||
// "key": keys[1],
|
||||
// "service": services[1],
|
||||
// "resourceID": resourceID,
|
||||
// "resourceUUIDs": [uuids[2]],
|
||||
// "isLoading": false,
|
||||
// "timeAdded": new Date().getTime(),
|
||||
// "msToLive": msToLive
|
||||
// 'key': keys[1],
|
||||
// 'service': services[1],
|
||||
// 'resourceID': resourceID,
|
||||
// 'resourceUUIDs': [uuids[2]],
|
||||
// 'isLoading': false,
|
||||
// 'timeAdded': new Date().getTime(),
|
||||
// 'msToLive': msToLive
|
||||
// }
|
||||
// };
|
||||
// deepFreeze(testState);
|
||||
@@ -57,29 +59,29 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// deepFreeze(errorState);
|
||||
//
|
||||
//
|
||||
// it("should return the current state when no valid actions have been made", () => {
|
||||
// it('should return the current state when no valid actions have been made', () => {
|
||||
// const action = new NullAction();
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
//
|
||||
// expect(newState).toEqual(testState);
|
||||
// });
|
||||
//
|
||||
// it("should start with an empty cache", () => {
|
||||
// it('should start with an empty cache', () => {
|
||||
// const action = new NullAction();
|
||||
// const initialState = responseCacheReducer(undefined, action);
|
||||
//
|
||||
// expect(initialState).toEqual(Object.create(null));
|
||||
// });
|
||||
//
|
||||
// describe("FIND_BY_ID", () => {
|
||||
// describe('FIND_BY_ID', () => {
|
||||
// const action = new ResponseCacheFindByIDAction(keys[0], services[0], resourceID);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the request to the cache", () => {
|
||||
// it('should add the request to the cache', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].key).toBe(keys[0]);
|
||||
@@ -87,28 +89,28 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// expect(newState[keys[0]].resourceID).toBe(resourceID);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to true", () => {
|
||||
// it('should set responsePending to true', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message or resourceUUID for the request", () => {
|
||||
// it('should remove any previous error message or resourceUUID for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs.length).toBe(0);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("FIND_ALL", () => {
|
||||
// describe('FIND_ALL', () => {
|
||||
// const action = new ResponseCacheFindAllAction(keys[0], services[0], resourceID, paginationOptions, sortOptions);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the request to the cache", () => {
|
||||
// it('should add the request to the cache', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].key).toBe(keys[0]);
|
||||
@@ -118,84 +120,84 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// expect(newState[keys[0]].sortOptions).toEqual(sortOptions);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to true", () => {
|
||||
// it('should set responsePending to true', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message or resourceUUIDs for the request", () => {
|
||||
// it('should remove any previous error message or resourceUUIDs for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs.length).toBe(0);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("SUCCESS", () => {
|
||||
// describe('SUCCESS', () => {
|
||||
// const successUUIDs = [uuids[0], uuids[2]];
|
||||
// const successTimeAdded = new Date().getTime();
|
||||
// const successMsToLive = 5;
|
||||
// const action = new ResponseCacheSuccessAction(keys[0], successUUIDs, successTimeAdded, successMsToLive);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the response to the cached request", () => {
|
||||
// it('should add the response to the cached request', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs).toBe(successUUIDs);
|
||||
// expect(newState[keys[0]].timeAdded).toBe(successTimeAdded);
|
||||
// expect(newState[keys[0]].msToLive).toBe(successMsToLive);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to false", () => {
|
||||
// it('should set responsePending to false', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(false);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message for the request", () => {
|
||||
// it('should remove any previous error message for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("ERROR", () => {
|
||||
// describe('ERROR', () => {
|
||||
// const errorMsg = 'errorMsg';
|
||||
// const action = new ResponseCacheErrorAction(keys[0], errorMsg);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should set an error message for the request", () => {
|
||||
// it('should set an error message for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].errorMessage).toBe(errorMsg);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to false", () => {
|
||||
// it('should set responsePending to false', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(false);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("REMOVE", () => {
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// describe('REMOVE', () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// const action = new ResponseCacheRemoveAction(keys[0]);
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should remove the specified request from the cache", () => {
|
||||
// it('should remove the specified request from the cache', () => {
|
||||
// const action = new ResponseCacheRemoveAction(keys[0]);
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(testState[keys[0]]).not.toBeUndefined();
|
||||
// expect(newState[keys[0]]).toBeUndefined();
|
||||
// });
|
||||
//
|
||||
// it("shouldn't do anything when the specified key isn't cached", () => {
|
||||
// const wrongKey = "this isn't cached";
|
||||
// it('shouldn't do anything when the specified key isn't cached', () => {
|
||||
// const wrongKey = 'this isn't cached';
|
||||
// const action = new ResponseCacheRemoveAction(wrongKey);
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(testState[wrongKey]).toBeUndefined();
|
||||
@@ -203,16 +205,16 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("RESET_TIMESTAMPS", () => {
|
||||
// describe('RESET_TIMESTAMPS', () => {
|
||||
// const newTimeStamp = new Date().getTime();
|
||||
// const action = new ResetResponseCacheTimestampsAction(newTimeStamp);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should set the timestamp of all requests in the cache", () => {
|
||||
// it('should set the timestamp of all requests in the cache', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// Object.keys(newState).forEach((key) => {
|
||||
// expect(newState[key].timeAdded).toEqual(newTimeStamp);
|
||||
|
23
src/app/core/cache/response-cache.reducer.ts
vendored
23
src/app/core/cache/response-cache.reducer.ts
vendored
@@ -2,10 +2,10 @@ import {
|
||||
ResponseCacheAction, ResponseCacheActionTypes,
|
||||
ResponseCacheRemoveAction, ResetResponseCacheTimestampsAction,
|
||||
ResponseCacheAddAction
|
||||
} from "./response-cache.actions";
|
||||
import { CacheEntry } from "./cache-entry";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { Response } from "./response-cache.models";
|
||||
} from './response-cache.actions';
|
||||
import { CacheEntry } from './cache-entry';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* An entry in the ResponseCache
|
||||
@@ -41,15 +41,15 @@ export const responseCacheReducer = (state = initialState, action: ResponseCache
|
||||
switch (action.type) {
|
||||
|
||||
case ResponseCacheActionTypes.ADD: {
|
||||
return addToCache(state, <ResponseCacheAddAction> action);
|
||||
return addToCache(state, action as ResponseCacheAddAction);
|
||||
}
|
||||
|
||||
case ResponseCacheActionTypes.REMOVE: {
|
||||
return removeFromCache(state, <ResponseCacheRemoveAction> action);
|
||||
return removeFromCache(state, action as ResponseCacheRemoveAction);
|
||||
}
|
||||
|
||||
case ResponseCacheActionTypes.RESET_TIMESTAMPS: {
|
||||
return resetResponseCacheTimestamps(state, <ResetResponseCacheTimestampsAction>action)
|
||||
return resetResponseCacheTimestamps(state, action as ResetResponseCacheTimestampsAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -81,12 +81,11 @@ function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction):
|
||||
*/
|
||||
function removeFromCache(state: ResponseCacheState, action: ResponseCacheRemoveAction): ResponseCacheState {
|
||||
if (hasValue(state[action.payload])) {
|
||||
let newCache = Object.assign({}, state);
|
||||
const newCache = Object.assign({}, state);
|
||||
delete newCache[action.payload];
|
||||
|
||||
return newCache;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -102,8 +101,8 @@ function removeFromCache(state: ResponseCacheState, action: ResponseCacheRemoveA
|
||||
* the new state, with all timeAdded timestamps set to the specified value
|
||||
*/
|
||||
function resetResponseCacheTimestamps(state: ResponseCacheState, action: ResetResponseCacheTimestampsAction): ResponseCacheState {
|
||||
let newState = Object.create(null);
|
||||
Object.keys(state).forEach(key => {
|
||||
const newState = Object.create(null);
|
||||
Object.keys(state).forEach((key) => {
|
||||
newState[key] = Object.assign({}, state[key], {
|
||||
timeAdded: action.payload
|
||||
});
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import { ResponseCacheService } from "./response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { ResponseCacheState, ResponseCacheEntry } from "./response-cache.reducer";
|
||||
import { OpaqueToken } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { OpaqueToken } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
// describe("ResponseCacheService", () => {
|
||||
import { ResponseCacheService } from './response-cache.service';
|
||||
import { ResponseCacheState, ResponseCacheEntry } from './response-cache.reducer';
|
||||
|
||||
// describe('ResponseCacheService', () => {
|
||||
// let service: ResponseCacheService;
|
||||
// let store: Store<ResponseCacheState>;
|
||||
//
|
||||
// const keys = ["125c17f89046283c5f0640722aac9feb", "a06c3006a41caec5d635af099b0c780c"];
|
||||
// const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c'];
|
||||
// const serviceTokens = [new OpaqueToken('service1'), new OpaqueToken('service2')];
|
||||
// const resourceID = "9978";
|
||||
// const paginationOptions = { "resultsPerPage": 10, "currentPage": 1 };
|
||||
// const sortOptions = { "field": "id", "direction": 0 };
|
||||
// const resourceID = '9978';
|
||||
// const paginationOptions = { 'resultsPerPage': 10, 'currentPage': 1 };
|
||||
// const sortOptions = { 'field': 'id', 'direction': 0 };
|
||||
// const timestamp = new Date().getTime();
|
||||
// const validCacheEntry = (key) => {
|
||||
// return {
|
||||
@@ -36,33 +37,33 @@ import { Observable } from "rxjs";
|
||||
// spyOn(window, 'Date').and.returnValue({ getTime: () => timestamp });
|
||||
// });
|
||||
//
|
||||
// describe("findAll", () => {
|
||||
// describe('findAll', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||
// spyOn(service, 'get').and.callFake((key) => Observable.of({key: key}));
|
||||
// });
|
||||
// describe("if the key isn't cached", () => {
|
||||
// describe('if the key isn't cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(false);
|
||||
// spyOn(service, 'has').and.returnValue(false);
|
||||
// });
|
||||
// it("should dispatch a FIND_ALL action with the key, service, scopeID, paginationOptions and sortOptions", () => {
|
||||
// it('should dispatch a FIND_ALL action with the key, service, scopeID, paginationOptions and sortOptions', () => {
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith(new ResponseCacheFindAllAction(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions))
|
||||
// });
|
||||
// it("should return an observable of the newly cached request with the specified key", () => {
|
||||
// it('should return an observable of the newly cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
// });
|
||||
// });
|
||||
// describe("if the key is already cached", () => {
|
||||
// describe('if the key is already cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(true);
|
||||
// spyOn(service, 'has').and.returnValue(true);
|
||||
// });
|
||||
// it("shouldn't dispatch anything", () => {
|
||||
// it('shouldn't dispatch anything', () => {
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||
// expect(store.dispatch).not.toHaveBeenCalled();
|
||||
// });
|
||||
// it("should return an observable of the existing cached request with the specified key", () => {
|
||||
// it('should return an observable of the existing cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
@@ -70,33 +71,33 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("findById", () => {
|
||||
// describe('findById', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||
// spyOn(service, 'get').and.callFake((key) => Observable.of({key: key}));
|
||||
// });
|
||||
// describe("if the key isn't cached", () => {
|
||||
// describe('if the key isn't cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(false);
|
||||
// spyOn(service, 'has').and.returnValue(false);
|
||||
// });
|
||||
// it("should dispatch a FIND_BY_ID action with the key, service, and resourceID", () => {
|
||||
// it('should dispatch a FIND_BY_ID action with the key, service, and resourceID', () => {
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith(new ResponseCacheFindByIDAction(keys[0], serviceTokens[0], resourceID))
|
||||
// });
|
||||
// it("should return an observable of the newly cached request with the specified key", () => {
|
||||
// it('should return an observable of the newly cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
// });
|
||||
// });
|
||||
// describe("if the key is already cached", () => {
|
||||
// describe('if the key is already cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(true);
|
||||
// spyOn(service, 'has').and.returnValue(true);
|
||||
// });
|
||||
// it("shouldn't dispatch anything", () => {
|
||||
// it('shouldn't dispatch anything', () => {
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID);
|
||||
// expect(store.dispatch).not.toHaveBeenCalled();
|
||||
// });
|
||||
// it("should return an observable of the existing cached request with the specified key", () => {
|
||||
// it('should return an observable of the existing cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
@@ -104,9 +105,9 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("get", () => {
|
||||
// it("should return an observable of the cached request with the specified key", () => {
|
||||
// spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||
// describe('get', () => {
|
||||
// it('should return an observable of the cached request with the specified key', () => {
|
||||
// spyOn(store, 'select').and.callFake((...args:Array<any>) => {
|
||||
// return Observable.of(validCacheEntry(args[args.length - 1]));
|
||||
// });
|
||||
//
|
||||
@@ -115,8 +116,8 @@ import { Observable } from "rxjs";
|
||||
// expect(testObj.key).toEqual(keys[1]);
|
||||
// });
|
||||
//
|
||||
// it("should not return a cached request that has exceeded its time to live", () => {
|
||||
// spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||
// it('should not return a cached request that has exceeded its time to live', () => {
|
||||
// spyOn(store, 'select').and.callFake((...args:Array<any>) => {
|
||||
// return Observable.of(invalidCacheEntry(args[args.length - 1]));
|
||||
// });
|
||||
//
|
||||
@@ -127,18 +128,18 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("has", () => {
|
||||
// it("should return true if the request with the supplied key is cached and still valid", () => {
|
||||
// describe('has', () => {
|
||||
// it('should return true if the request with the supplied key is cached and still valid', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(validCacheEntry(keys[1])));
|
||||
// expect(service.has(keys[1])).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should return false if the request with the supplied key isn't cached", () => {
|
||||
// it('should return false if the request with the supplied key isn't cached', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(undefined));
|
||||
// expect(service.has(keys[1])).toBe(false);
|
||||
// });
|
||||
//
|
||||
// it("should return false if the request with the supplied key is cached but has exceeded its time to live", () => {
|
||||
// it('should return false if the request with the supplied key is cached but has exceeded its time to live', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry(keys[1])));
|
||||
// expect(service.has(keys[1])).toBe(false);
|
||||
// });
|
||||
|
38
src/app/core/cache/response-cache.service.ts
vendored
38
src/app/core/cache/response-cache.service.ts
vendored
@@ -1,15 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import {
|
||||
ResponseCacheState, ResponseCacheEntry
|
||||
} from "./response-cache.reducer";
|
||||
import { Observable } from "rxjs";
|
||||
import { hasNoValue } from "../../shared/empty.util";
|
||||
import {
|
||||
ResponseCacheRemoveAction,
|
||||
ResponseCacheAddAction
|
||||
} from "./response-cache.actions";
|
||||
import { Response } from "./response-cache.models";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ResponseCacheState, ResponseCacheEntry } from './response-cache.reducer';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* A service to interact with the response cache
|
||||
@@ -18,13 +15,13 @@ import { Response } from "./response-cache.models";
|
||||
export class ResponseCacheService {
|
||||
constructor(
|
||||
private store: Store<ResponseCacheState>
|
||||
) {}
|
||||
) { }
|
||||
|
||||
add(key: string, response: Response, msToLive: number): Observable<ResponseCacheEntry> {
|
||||
if (!this.has(key)) {
|
||||
// this.store.dispatch(new ResponseCacheFindAllAction(key, service, scopeID, paginationOptions, sortOptions));
|
||||
this.store.dispatch(new ResponseCacheAddAction(key, response, new Date().getTime(), msToLive));
|
||||
}
|
||||
if (!this.has(key)) {
|
||||
// this.store.dispatch(new ResponseCacheFindAllAction(key, service, scopeID, paginationOptions, sortOptions));
|
||||
this.store.dispatch(new ResponseCacheAddAction(key, response, new Date().getTime(), msToLive));
|
||||
}
|
||||
return this.get(key);
|
||||
}
|
||||
|
||||
@@ -38,7 +35,7 @@ export class ResponseCacheService {
|
||||
*/
|
||||
get(key: string): Observable<ResponseCacheEntry> {
|
||||
return this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
.filter(entry => this.isValid(entry))
|
||||
.filter((entry) => this.isValid(entry))
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
@@ -56,7 +53,7 @@ export class ResponseCacheService {
|
||||
|
||||
this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
.take(1)
|
||||
.subscribe(entry => {
|
||||
.subscribe((entry) => {
|
||||
result = this.isValid(entry);
|
||||
});
|
||||
|
||||
@@ -75,8 +72,7 @@ export class ResponseCacheService {
|
||||
private isValid(entry: ResponseCacheEntry): boolean {
|
||||
if (hasNoValue(entry)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const timeOutdated = entry.timeAdded + entry.msToLive;
|
||||
const isOutDated = new Date().getTime() > timeOutdated;
|
||||
if (isOutDated) {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { EffectsModule } from "@ngrx/effects";
|
||||
import { ObjectCacheEffects } from "./data/object-cache.effects";
|
||||
import { RequestCacheEffects } from "./data/request-cache.effects";
|
||||
import { HrefIndexEffects } from "./index/href-index.effects";
|
||||
import { RequestEffects } from "./data/request.effects";
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheEffects } from './data/object-cache.effects';
|
||||
import { RequestCacheEffects } from './data/request-cache.effects';
|
||||
import { HrefIndexEffects } from './index/href-index.effects';
|
||||
import { RequestEffects } from './data/request.effects';
|
||||
|
||||
export const coreEffects = [
|
||||
EffectsModule.run(RequestEffects),
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
import { isNotEmpty } from "../shared/empty.util";
|
||||
import { FooterComponent } from "./footer/footer.component";
|
||||
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { ObjectCacheService } from "./cache/object-cache.service";
|
||||
import { ResponseCacheService } from "./cache/response-cache.service";
|
||||
import { CollectionDataService } from "./data/collection-data.service";
|
||||
import { ItemDataService } from "./data/item-data.service";
|
||||
import { RequestService } from "./data/request.service";
|
||||
import { RemoteDataBuildService } from "./cache/builders/remote-data-build.service";
|
||||
import { CommunityDataService } from "./data/community-data.service";
|
||||
import { PaginationComponentOptions } from "../shared/pagination/pagination-component-options.model";
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { ObjectCacheService } from './cache/object-cache.service';
|
||||
import { ResponseCacheService } from './cache/response-cache.service';
|
||||
import { CollectionDataService } from './data/collection-data.service';
|
||||
import { ItemDataService } from './data/item-data.service';
|
||||
import { RequestService } from './data/request.service';
|
||||
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
|
||||
import { CommunityDataService } from './data/community-data.service';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
@@ -40,18 +41,12 @@ const PROVIDERS = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [ ...IMPORTS ],
|
||||
imports: [...IMPORTS],
|
||||
declarations: [...DECLARATIONS],
|
||||
exports: [...EXPORTS],
|
||||
providers: [...PROVIDERS]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error(
|
||||
'CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
@@ -61,4 +56,11 @@ export class CoreModule {
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { CacheState, cacheReducer } from "./cache/cache.reducers";
|
||||
import { IndexState, indexReducer } from "./index/index.reducers";
|
||||
import { DataState, dataReducer } from "./data/data.reducers";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { CacheState, cacheReducer } from './cache/cache.reducers';
|
||||
import { IndexState, indexReducer } from './index/index.reducers';
|
||||
import { DataState, dataReducer } from './data/data.reducers';
|
||||
|
||||
export interface CoreState {
|
||||
cache: CacheState,
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Collection } from "../shared/collection.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { NormalizedCollection } from "../cache/models/normalized-collection.model";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService extends DataService<NormalizedCollection, Collection> {
|
||||
@@ -23,7 +24,7 @@ export class CollectionDataService extends DataService<NormalizedCollection, Col
|
||||
protected store: Store<CoreState>,
|
||||
@Inject(GLOBAL_CONFIG) EnvConfig: GlobalConfig
|
||||
) {
|
||||
super(NormalizedCollection, EnvConfig);
|
||||
super(NormalizedCollection, EnvConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Community } from "../shared/community.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { NormalizedCommunity } from "../cache/models/normalized-community.model";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityDataService extends DataService<NormalizedCommunity, Community> {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { RequestState, requestReducer } from "./request.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { RequestState, requestReducer } from './request.reducer';
|
||||
|
||||
export interface DataState {
|
||||
request: RequestState
|
||||
|
@@ -1,18 +1,18 @@
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { hasValue, isNotEmpty } from "../../shared/empty.util";
|
||||
import { RemoteData } from "./remote-data";
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, Request } from "./request.models";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { RequestConfigureAction, RequestExecuteAction } from "./request.actions";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { Inject } from "@angular/core";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { RESTURLCombiner } from "../url-combiner/rest-url-combiner";
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, Request } from './request.models';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { Inject } from '@angular/core';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
|
||||
export abstract class DataService<TNormalized extends CacheableObject, TDomain> {
|
||||
protected abstract objectCache: ObjectCacheService;
|
||||
@@ -32,17 +32,16 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
|
||||
protected getFindAllHref(options: FindAllOptions = {}): string {
|
||||
let result;
|
||||
let args = [];
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.scopeID)) {
|
||||
result = this.browseEndpoint;
|
||||
args.push(`scope=${options.scopeID}`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result = this.resourceEndpoint;
|
||||
}
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === "number") {
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args.push(`page=${options.currentPage - 1}`);
|
||||
}
|
||||
@@ -65,7 +64,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
return new RESTURLCombiner(this.EnvConfig, result).toString();
|
||||
}
|
||||
|
||||
findAll(options: FindAllOptions = {}): RemoteData<Array<TDomain>> {
|
||||
findAll(options: FindAllOptions = {}): RemoteData<TDomain[]> {
|
||||
const href = this.getFindAllHref(options);
|
||||
const request = new FindAllRequest(href, options);
|
||||
this.requestService.configure(request);
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Item } from "../shared/item.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { NormalizedItem } from "../cache/models/normalized-item.model";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
@@ -22,7 +24,7 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
@Inject(GLOBAL_CONFIG) EnvConfig: GlobalConfig
|
||||
) {
|
||||
) {
|
||||
super(NormalizedItem, EnvConfig);
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { StoreActionTypes } from "../../store.actions";
|
||||
import { ResetObjectCacheTimestampsAction } from "../cache/object-cache.actions";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
import { StoreActionTypes } from '../../store.actions';
|
||||
import { ResetObjectCacheTimestampsAction } from '../cache/object-cache.actions';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectCacheEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions
|
||||
) { }
|
||||
|
||||
/**
|
||||
* When the store is rehydrated in the browser, set all cache
|
||||
* timestamps to "now", because the time zone of the server can
|
||||
* timestamps to 'now', because the time zone of the server can
|
||||
* differ from the client.
|
||||
*
|
||||
* This assumes that the server cached everything a negligible
|
||||
@@ -22,4 +19,6 @@ export class ObjectCacheEffects {
|
||||
.ofType(StoreActionTypes.REHYDRATE)
|
||||
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()));
|
||||
|
||||
constructor(private actions$: Actions) { }
|
||||
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
|
||||
export enum RemoteDataState {
|
||||
RequestPending = <any> "RequestPending",
|
||||
ResponsePending = <any> "ResponsePending",
|
||||
Failed = <any> "Failed",
|
||||
Success = <any> "Success"
|
||||
RequestPending = 'RequestPending' as any,
|
||||
ResponsePending = 'ResponsePending' as any,
|
||||
Failed = 'Failed' as any,
|
||||
Success = 'Success' as any
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,14 +33,11 @@ export class RemoteData<T> {
|
||||
(requestPending, responsePending, isSuccessFul) => {
|
||||
if (requestPending) {
|
||||
return RemoteDataState.RequestPending
|
||||
}
|
||||
else if (responsePending) {
|
||||
} else if (responsePending) {
|
||||
return RemoteDataState.ResponsePending
|
||||
}
|
||||
else if (!isSuccessFul) {
|
||||
} else if (!isSuccessFul) {
|
||||
return RemoteDataState.Failed
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return RemoteDataState.Success
|
||||
}
|
||||
}
|
||||
@@ -47,36 +45,26 @@ export class RemoteData<T> {
|
||||
}
|
||||
|
||||
get isRequestPending(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.RequestPending)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.RequestPending).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get isResponsePending(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.ResponsePending)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.ResponsePending).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get isLoading(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => {
|
||||
return state == RemoteDataState.RequestPending
|
||||
|| state === RemoteDataState.ResponsePending
|
||||
})
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => {
|
||||
return state === RemoteDataState.RequestPending
|
||||
|| state === RemoteDataState.ResponsePending
|
||||
}).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get hasFailed(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.Failed)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.Failed).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get hasSucceeded(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.Success)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.Success).distinctUntilChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,20 +1,15 @@
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { ObjectCacheActionTypes } from "../cache/object-cache.actions";
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from "../../../config";
|
||||
import { ResetResponseCacheTimestampsAction } from "../cache/response-cache.actions";
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheActionTypes } from '../cache/object-cache.actions';
|
||||
import { ResetResponseCacheTimestampsAction } from '../cache/response-cache.actions';
|
||||
|
||||
@Injectable()
|
||||
export class RequestCacheEffects {
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* When the store is rehydrated in the browser, set all cache
|
||||
* timestamps to "now", because the time zone of the server can
|
||||
* timestamps to 'now', because the time zone of the server can
|
||||
* differ from the client.
|
||||
*
|
||||
* This assumes that the server cached everything a negligible
|
||||
@@ -31,4 +26,7 @@ export class RequestCacheEffects {
|
||||
@Effect() fixTimestampsOnRehydrate = this.actions$
|
||||
.ofType(ObjectCacheActionTypes.RESET_TIMESTAMPS)
|
||||
.map(() => new ResetResponseCacheTimestampsAction(new Date().getTime()));
|
||||
|
||||
constructor(private actions$: Actions, ) { }
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { Request } from "./request.models";
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Request } from './request.models';
|
||||
|
||||
/**
|
||||
* The list of RequestAction type definitions
|
||||
@@ -12,6 +12,7 @@ export const RequestActionTypes = {
|
||||
COMPLETE: type('dspace/core/data/request/COMPLETE')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class RequestConfigureAction implements Action {
|
||||
type = RequestActionTypes.CONFIGURE;
|
||||
payload: Request<CacheableObject>;
|
||||
@@ -49,6 +50,7 @@ export class RequestCompleteAction implements Action {
|
||||
this.payload = key;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all RequestActions
|
||||
|
@@ -1,26 +1,27 @@
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { Observable } from "rxjs";
|
||||
import { Response, SuccessResponse, ErrorResponse } from "../cache/response-cache.models";
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from "../../shared/empty.util";
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from "../../../config";
|
||||
import { RequestEntry } from "./request.reducer";
|
||||
import {
|
||||
RequestActionTypes, RequestExecuteAction,
|
||||
RequestCompleteAction
|
||||
} from "./request.actions";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { RequestService } from "./request.service";
|
||||
import { NormalizedObjectFactory } from "../cache/models/normalized-object-factory";
|
||||
import { ResourceType } from "../shared/resource-type";
|
||||
import { RequestError } from "./request.models";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { NormalizedObject } from "../cache/models/normalized-object.model";
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Response, SuccessResponse, ErrorResponse } from '../cache/response-cache.models';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestActionTypes, RequestExecuteAction, RequestCompleteAction } from './request.actions';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
|
||||
import { ResourceType } from '../shared/resource-type';
|
||||
import { RequestError } from './request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from '../../../config';
|
||||
|
||||
function isObjectLevel(halObj: any) {
|
||||
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
|
||||
@@ -38,23 +39,14 @@ function flattenSingleKeyObject(obj: any): any {
|
||||
return obj[keys[0]];
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
class ProcessRequestDTO {
|
||||
[key: string]: NormalizedObject[]
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RequestEffects {
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private objectCache: ObjectCacheService,
|
||||
private responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
) { }
|
||||
|
||||
@Effect() execute = this.actions$
|
||||
.ofType(RequestActionTypes.EXECUTE)
|
||||
.flatMap((action: RequestExecuteAction) => {
|
||||
@@ -65,48 +57,52 @@ export class RequestEffects {
|
||||
return this.restApi.get(entry.request.href)
|
||||
.map((data: DSpaceRESTV2Response) => {
|
||||
const processRequestDTO = this.process(data.payload, entry.request.href);
|
||||
const uuids = flattenSingleKeyObject(processRequestDTO).map(no => no.uuid);
|
||||
const uuids = flattenSingleKeyObject(processRequestDTO).map((no) => no.uuid);
|
||||
return new SuccessResponse(uuids, data.statusCode, this.processPageInfo(data.payload.page))
|
||||
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href))
|
||||
.catch((error: RequestError) => Observable.of(new ErrorResponse(error))
|
||||
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href)));
|
||||
});
|
||||
|
||||
protected process(data: any, requestHref: string): ProcessRequestDTO {
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private objectCache: ObjectCacheService,
|
||||
private responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
) { }
|
||||
|
||||
protected process(data: any, requestHref: string): ProcessRequestDTO {
|
||||
|
||||
if (isNotEmpty(data)) {
|
||||
if (isPaginatedResponse(data)) {
|
||||
return this.process(data._embedded, requestHref);
|
||||
}
|
||||
else if (isObjectLevel(data)) {
|
||||
return { "topLevel": this.deserializeAndCache(data, requestHref) };
|
||||
}
|
||||
else {
|
||||
let result = new ProcessRequestDTO();
|
||||
} else if (isObjectLevel(data)) {
|
||||
return { topLevel: this.deserializeAndCache(data, requestHref) };
|
||||
} else {
|
||||
const result = new ProcessRequestDTO();
|
||||
if (Array.isArray(data)) {
|
||||
result['topLevel'] = [];
|
||||
data.forEach(datum => {
|
||||
result.topLevel = [];
|
||||
data.forEach((datum) => {
|
||||
if (isPaginatedResponse(datum)) {
|
||||
const obj = this.process(datum, requestHref);
|
||||
result['topLevel'] = [...result['topLevel'], ...flattenSingleKeyObject(obj)];
|
||||
}
|
||||
else {
|
||||
result['topLevel'] = [...result['topLevel'], ...this.deserializeAndCache(datum, requestHref)];
|
||||
result.topLevel = [...result.topLevel, ...flattenSingleKeyObject(obj)];
|
||||
} else {
|
||||
result.topLevel = [...result.topLevel, ...this.deserializeAndCache(datum, requestHref)];
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Object.keys(data)
|
||||
.filter(property => data.hasOwnProperty(property))
|
||||
.filter(property => hasValue(data[property]))
|
||||
.forEach(property => {
|
||||
.filter((property) => data.hasOwnProperty(property))
|
||||
.filter((property) => hasValue(data[property]))
|
||||
.forEach((property) => {
|
||||
if (isPaginatedResponse(data[property])) {
|
||||
const obj = this.process(data[property], requestHref);
|
||||
result[property] = flattenSingleKeyObject(obj);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result[property] = this.deserializeAndCache(data[property], requestHref);
|
||||
}
|
||||
});
|
||||
@@ -117,13 +113,13 @@ export class RequestEffects {
|
||||
}
|
||||
|
||||
protected deserializeAndCache(obj, requestHref: string): NormalizedObject[] {
|
||||
if(Array.isArray(obj)) {
|
||||
if (Array.isArray(obj)) {
|
||||
let result = [];
|
||||
obj.forEach(o => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
obj.forEach((o) => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
return result;
|
||||
}
|
||||
|
||||
let type: ResourceType = obj["type"];
|
||||
const type: ResourceType = obj.type;
|
||||
if (hasValue(type)) {
|
||||
const normObjConstructor = NormalizedObjectFactory.getConstructor(type);
|
||||
|
||||
@@ -134,11 +130,11 @@ export class RequestEffects {
|
||||
if (isNotEmpty(obj._embedded)) {
|
||||
processed = this.process(obj._embedded, requestHref);
|
||||
}
|
||||
let normalizedObj = serializer.deserialize(obj);
|
||||
const normalizedObj = serializer.deserialize(obj);
|
||||
|
||||
if (isNotEmpty(processed)) {
|
||||
let linksOnly = {};
|
||||
Object.keys(processed).forEach(key => {
|
||||
const linksOnly = {};
|
||||
Object.keys(processed).forEach((key) => {
|
||||
linksOnly[key] = processed[key].map((no: NormalizedObject) => no.self);
|
||||
});
|
||||
Object.assign(normalizedObj, linksOnly);
|
||||
@@ -147,16 +143,14 @@ export class RequestEffects {
|
||||
this.addToObjectCache(normalizedObj, requestHref);
|
||||
return [normalizedObj];
|
||||
|
||||
}
|
||||
else {
|
||||
//TODO move check to Validator?
|
||||
} else {
|
||||
// TODO: move check to Validator?
|
||||
// throw new Error(`The server returned an object with an unknown a known type: ${type}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//TODO move check to Validator
|
||||
} else {
|
||||
// TODO: move check to Validator
|
||||
// throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
|
||||
return [];
|
||||
}
|
||||
@@ -172,10 +166,10 @@ export class RequestEffects {
|
||||
protected processPageInfo(pageObj: any): PageInfo {
|
||||
if (isNotEmpty(pageObj)) {
|
||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { SortOptions } from "../cache/models/sort-options.model";
|
||||
import { PaginationComponentOptions } from "../../shared/pagination/pagination-component-options.model";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { SortOptions } from '../cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Request<T> {
|
||||
constructor(
|
||||
public href: string,
|
||||
) {}
|
||||
) { }
|
||||
}
|
||||
|
||||
export class FindByIDRequest<T> extends Request<T> {
|
||||
@@ -36,3 +37,4 @@ export class FindAllRequest<T> extends Request<T> {
|
||||
export class RequestError extends Error {
|
||||
statusText: string;
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import {
|
||||
RequestActionTypes, RequestAction, RequestConfigureAction,
|
||||
RequestExecuteAction, RequestCompleteAction
|
||||
} from "./request.actions";
|
||||
import { Request } from "./request.models";
|
||||
} from './request.actions';
|
||||
import { Request } from './request.models';
|
||||
|
||||
export class RequestEntry {
|
||||
request: Request<CacheableObject>;
|
||||
@@ -12,7 +12,6 @@ export class RequestEntry {
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface RequestState {
|
||||
[key: string]: RequestEntry
|
||||
}
|
||||
@@ -24,15 +23,15 @@ export const requestReducer = (state = initialState, action: RequestAction): Req
|
||||
switch (action.type) {
|
||||
|
||||
case RequestActionTypes.CONFIGURE: {
|
||||
return configureRequest(state, <RequestConfigureAction> action);
|
||||
return configureRequest(state, action as RequestConfigureAction);
|
||||
}
|
||||
|
||||
case RequestActionTypes.EXECUTE: {
|
||||
return executeRequest(state, <RequestExecuteAction> action);
|
||||
return executeRequest(state, action as RequestExecuteAction);
|
||||
}
|
||||
|
||||
case RequestActionTypes.COMPLETE: {
|
||||
return completeRequest(state, <RequestCompleteAction> action);
|
||||
return completeRequest(state, action as RequestCompleteAction);
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@@ -1,16 +1,20 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { RequestEntry, RequestState } from "./request.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Request } from "./request.models";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { RequestConfigureAction, RequestExecuteAction } from "./request.actions";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { ResponseCacheEntry } from "../cache/response-cache.reducer";
|
||||
import { request } from "http";
|
||||
import { SuccessResponse } from "../cache/response-cache.models";
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { request } from 'http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { RequestEntry, RequestState } from './request.reducer';
|
||||
import { Request } from './request.models';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { SuccessResponse } from '../cache/response-cache.models';
|
||||
|
||||
@Injectable()
|
||||
export class RequestService {
|
||||
@@ -26,9 +30,9 @@ export class RequestService {
|
||||
let isPending = false;
|
||||
this.store.select<RequestEntry>('core', 'data', 'request', href)
|
||||
.take(1)
|
||||
.subscribe((re: RequestEntry) => {
|
||||
.subscribe((re: RequestEntry) => {
|
||||
isPending = (hasValue(re) && !re.completed)
|
||||
});
|
||||
});
|
||||
|
||||
return isPending;
|
||||
}
|
||||
@@ -41,14 +45,14 @@ export class RequestService {
|
||||
let isCached = this.objectCache.hasBySelfLink(request.href);
|
||||
|
||||
if (!isCached && this.responseCache.has(request.href)) {
|
||||
//if it isn't cached it may be a list endpoint, if so verify
|
||||
//every object included in the response is still cached
|
||||
// if it isn't cached it may be a list endpoint, if so verify
|
||||
// every object included in the response is still cached
|
||||
this.responseCache.get(request.href)
|
||||
.take(1)
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
|
||||
.map((resourceUUIDs: Array<string>) => resourceUUIDs.every(uuid => this.objectCache.has(uuid)))
|
||||
.subscribe(c => isCached = c);
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.map((resourceUUIDs: string[]) => resourceUUIDs.every((uuid) => this.objectCache.has(uuid)))
|
||||
.subscribe((c) => isCached = c);
|
||||
}
|
||||
|
||||
const isPending = this.isPending(request.href);
|
||||
|
@@ -16,8 +16,7 @@
|
||||
"description": "Object of links with the rels as the keys",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"$ref": "#/definitions/linkObject"
|
||||
},
|
||||
{
|
||||
@@ -43,8 +42,7 @@
|
||||
"$ref": "http://hyperschema.org/core/base#/definitions/name"
|
||||
},
|
||||
"href": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [{
|
||||
"$ref": "http://hyperschema.org/core/link#/definitions/href"
|
||||
},
|
||||
{
|
||||
@@ -71,8 +69,7 @@
|
||||
"description": "An embedded HAL resource",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [{
|
||||
"$ref": "#"
|
||||
},
|
||||
{
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DSpaceRESTv2Serializer } from "./dspace-rest-v2.serializer";
|
||||
import { autoserialize, autoserializeAs } from "cerialize";
|
||||
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { DSpaceRESTv2Serializer } from './dspace-rest-v2.serializer';
|
||||
|
||||
class TestModel {
|
||||
@autoserialize
|
||||
@@ -9,55 +10,54 @@ class TestModel {
|
||||
name: string;
|
||||
|
||||
@autoserializeAs(TestModel)
|
||||
parents?: Array<TestModel>;
|
||||
parents?: TestModel[];
|
||||
}
|
||||
|
||||
const testModels = [
|
||||
{
|
||||
"id": "d4466d54-d73b-4d8f-b73f-c702020baa14",
|
||||
"name": "Model 1",
|
||||
id: 'd4466d54-d73b-4d8f-b73f-c702020baa14',
|
||||
name: 'Model 1',
|
||||
},
|
||||
{
|
||||
"id": "752a1250-949a-46ad-9bea-fbc45f0b656d",
|
||||
"name": "Model 2",
|
||||
id: '752a1250-949a-46ad-9bea-fbc45f0b656d',
|
||||
name: 'Model 2',
|
||||
}
|
||||
];
|
||||
|
||||
const testResponses = [
|
||||
{
|
||||
"_links": {
|
||||
"self": "/testmodels/9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"parents": [
|
||||
{ "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" },
|
||||
{ "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" }
|
||||
_links: {
|
||||
self: '/testmodels/9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
parents: [
|
||||
{ href: '/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78' },
|
||||
{ href: '/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5' }
|
||||
]
|
||||
},
|
||||
"id": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"type": "testModels",
|
||||
"name": "A Test Model"
|
||||
id: '9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
type: 'testModels',
|
||||
name: 'A Test Model'
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": "/testmodels/598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"parents": [
|
||||
{ "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" },
|
||||
{ "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" }
|
||||
_links: {
|
||||
self: '/testmodels/598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
parents: [
|
||||
{ href: '/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5' },
|
||||
{ href: '/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78' }
|
||||
]
|
||||
},
|
||||
"id": "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"type": "testModels",
|
||||
"name": "Another Test Model"
|
||||
id: '598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
type: 'testModels',
|
||||
name: 'Another Test Model'
|
||||
}
|
||||
];
|
||||
|
||||
const parentHrefRegex = /^\/testmodels\/(.+)$/g;
|
||||
|
||||
describe('DSpaceRESTv2Serializer', () => {
|
||||
|
||||
describe("DSpaceRESTv2Serializer", () => {
|
||||
describe('serialize', () => {
|
||||
|
||||
describe("serialize", () => {
|
||||
|
||||
it("should turn a model in to a valid document", () => {
|
||||
it('should turn a model in to a valid document', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = serializer.serialize(testModels[0]);
|
||||
expect(testModels[0].id).toBe(doc.id);
|
||||
@@ -66,9 +66,9 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("serializeArray", () => {
|
||||
describe('serializeArray', () => {
|
||||
|
||||
it("should turn an array of models in to a valid document", () => {
|
||||
it('should turn an array of models in to a valid document', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = serializer.serializeArray(testModels);
|
||||
|
||||
@@ -80,9 +80,9 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("deserialize", () => {
|
||||
describe('deserialize', () => {
|
||||
|
||||
it("should turn a valid document describing a single entity in to a valid model", () => {
|
||||
it('should turn a valid document describing a single entity in to a valid model', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const model = serializer.deserialize(testResponses[0]);
|
||||
|
||||
@@ -90,12 +90,12 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
expect(model.name).toBe(testResponses[0].name);
|
||||
});
|
||||
|
||||
//TODO cant implement/test this yet - depends on how relationships
|
||||
// TODO: cant implement/test this yet - depends on how relationships
|
||||
// will be handled in the rest api
|
||||
// it("should retain relationship information", () => {
|
||||
// it('should retain relationship information', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses[0],
|
||||
// '_embedded': testResponses[0],
|
||||
// };
|
||||
//
|
||||
// const model = serializer.deserialize(doc);
|
||||
@@ -112,7 +112,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// });
|
||||
|
||||
// TODO enable once validation is enabled in the serializer
|
||||
// it("should throw an error when dealing with an invalid document", () => {
|
||||
// it('should throw an error when dealing with an invalid document', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = testResponses[0];
|
||||
//
|
||||
@@ -121,7 +121,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// }).toThrow();
|
||||
// });
|
||||
|
||||
it("should throw an error when dealing with a document describing an array", () => {
|
||||
it('should throw an error when dealing with a document describing an array', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
expect(() => {
|
||||
serializer.deserialize(testResponses);
|
||||
@@ -130,13 +130,13 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("deserializeArray", () => {
|
||||
describe('deserializeArray', () => {
|
||||
|
||||
//TODO rewrite to incorporate normalisation.
|
||||
// it("should turn a valid document describing a collection of objects in to an array of valid models", () => {
|
||||
// TODO: rewrite to incorporate normalisation.
|
||||
// it('should turn a valid document describing a collection of objects in to an array of valid models', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses
|
||||
// '_embedded': testResponses
|
||||
// };
|
||||
//
|
||||
// const models = serializer.deserializeArray(doc);
|
||||
@@ -147,12 +147,12 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// expect(models[1].name).toBe(doc._embedded[1].name);
|
||||
// });
|
||||
|
||||
//TODO cant implement/test this yet - depends on how relationships
|
||||
// TODO: cant implement/test this yet - depends on how relationships
|
||||
// will be handled in the rest api
|
||||
// it("should retain relationship information", () => {
|
||||
// it('should retain relationship information', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses,
|
||||
// '_embedded': testResponses,
|
||||
// };
|
||||
//
|
||||
// const models = serializer.deserializeArray(doc);
|
||||
@@ -169,7 +169,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// });
|
||||
|
||||
// TODO enable once validation is enabled in the serializer
|
||||
// it("should throw an error when dealing with an invalid document", () => {
|
||||
// it('should throw an error when dealing with an invalid document', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = testResponses[0];
|
||||
//
|
||||
@@ -178,10 +178,10 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// }).toThrow();
|
||||
// });
|
||||
|
||||
it("should throw an error when dealing with a document describing a single model", () => {
|
||||
it('should throw an error when dealing with a document describing a single model', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = {
|
||||
"_embedded": testResponses[0]
|
||||
_embedded: testResponses[0]
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { Serialize, Deserialize } from "cerialize";
|
||||
import { Serializer } from "../serializer";
|
||||
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { hasNoValue, hasValue } from "../../shared/empty.util";
|
||||
import { Serialize, Deserialize } from 'cerialize';
|
||||
|
||||
import { Serializer } from '../serializer';
|
||||
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
||||
import { DSpaceRESTv2Validator } from './dspace-rest-v2.validator';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This Serializer turns responses from v2 of DSpace's REST API
|
||||
@@ -36,8 +37,8 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
* @param models The array of models to serialize
|
||||
* @returns An object to send to the backend
|
||||
*/
|
||||
serializeArray(models: Array<T>): any {
|
||||
return Serialize(models, this.modelType);
|
||||
serializeArray(models: T[]): any {
|
||||
return Serialize(models, this.modelType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +53,8 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
if (Array.isArray(response)) {
|
||||
throw new Error('Expected a single model, use deserializeArray() instead');
|
||||
}
|
||||
let normalized = Object.assign({}, response, this.normalizeLinks(response._links));
|
||||
return <T> Deserialize(normalized, this.modelType);
|
||||
const normalized = Object.assign({}, response, this.normalizeLinks(response._links));
|
||||
return Deserialize(normalized, this.modelType) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,28 +63,27 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
* @param response An object returned by the backend
|
||||
* @returns an array of models of type T
|
||||
*/
|
||||
deserializeArray(response: any): Array<T> {
|
||||
//TODO enable validation, once rest data stabilizes
|
||||
deserializeArray(response: any): T[] {
|
||||
// TODO: enable validation, once rest data stabilizes
|
||||
// new DSpaceRESTv2Validator(response).validate();
|
||||
if (!Array.isArray(response)) {
|
||||
throw new Error('Expected an Array, use deserialize() instead');
|
||||
}
|
||||
let normalized = response.map((resource) => {
|
||||
return Object.assign({}, resource, this.normalizeLinks(resource._links));
|
||||
const normalized = response.map((resource) => {
|
||||
return Object.assign({}, resource, this.normalizeLinks(resource._links));
|
||||
});
|
||||
|
||||
return <Array<T>> Deserialize(normalized, this.modelType);
|
||||
return Deserialize(normalized, this.modelType) as T[];
|
||||
}
|
||||
|
||||
private normalizeLinks(links:any): any {
|
||||
let normalizedLinks = links;
|
||||
for (let link in normalizedLinks) {
|
||||
private normalizeLinks(links: any): any {
|
||||
const normalizedLinks = links;
|
||||
for (const link in normalizedLinks) {
|
||||
if (Array.isArray(normalizedLinks[link])) {
|
||||
normalizedLinks[link] = normalizedLinks[link].map(linkedResource => {
|
||||
normalizedLinks[link] = normalizedLinks[link].map((linkedResource) => {
|
||||
return linkedResource.href;
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
normalizedLinks[link] = normalizedLinks[link].href;
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Http, RequestOptionsArgs } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RESTURLCombiner } from "../url-combiner/rest-url-combiner";
|
||||
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
||||
|
||||
/**
|
||||
* Service to access DSpace's REST API
|
||||
*/
|
||||
@Injectable()
|
||||
export class DSpaceRESTv2Service {
|
||||
|
||||
constructor(private http: Http, @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
||||
|
||||
}
|
||||
@@ -27,8 +29,8 @@ export class DSpaceRESTv2Service {
|
||||
*/
|
||||
get(absoluteURL: string, options?: RequestOptionsArgs): Observable<DSpaceRESTV2Response> {
|
||||
return this.http.get(absoluteURL, options)
|
||||
.map(res => ({ payload: res.json(), statusCode: res.statusText }))
|
||||
.catch(err => {
|
||||
.map((res) => ({ payload: res.json(), statusCode: res.statusText }))
|
||||
.catch((err) => {
|
||||
console.log('Error: ', err);
|
||||
return Observable.throw(err);
|
||||
});
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import * as schema from './dspace-rest-v2.schema.json'
|
||||
import { Validator } from "jsonschema";
|
||||
import { Validator } from 'jsonschema';
|
||||
|
||||
import schema from './dspace-rest-v2.schema.json'
|
||||
|
||||
/**
|
||||
* Verifies a document is a valid response from
|
||||
@@ -22,11 +23,10 @@ export class DSpaceRESTv2Validator {
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
const message = result.errors
|
||||
.map((error) => error.message)
|
||||
.join("\n");
|
||||
.join('\n');
|
||||
throw new Error(message);
|
||||
}
|
||||
else {
|
||||
throw new Error("JSON API validation failed for an unknown reason");
|
||||
} else {
|
||||
throw new Error('JSON API validation failed for an unknown reason');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,13 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../../node_modules/bootstrap/scss/_variables.scss';
|
||||
|
||||
$footer-bg: $gray-lighter;
|
||||
$footer-border: 1px solid darken($footer-bg, 10%);
|
||||
$footer-padding: $spacer * 1.5;
|
||||
|
||||
.footer {
|
||||
background-color: $footer-bg;
|
||||
border-top: $footer-border;
|
||||
text-align:center;
|
||||
border-top: $footer-border;
|
||||
text-align: center;
|
||||
padding: $footer-padding;
|
||||
|
||||
p {
|
||||
|
@@ -5,19 +5,23 @@ import {
|
||||
inject,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
DebugElement
|
||||
} from "@angular/core";
|
||||
} from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
|
||||
import { Store, StoreModule } from "@ngrx/store";
|
||||
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
|
||||
// Load the implementations that should be tested
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MockTranslateLoader } from "../../shared/testing/mock-translate-loader";
|
||||
import { MockTranslateLoader } from '../../shared/testing/mock-translate-loader';
|
||||
|
||||
let comp: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
@@ -1,18 +1,12 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-footer',
|
||||
styleUrls: ['footer.component.css'],
|
||||
styleUrls: ['footer.component.scss'],
|
||||
templateUrl: 'footer.component.html'
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
export class FooterComponent {
|
||||
|
||||
dateObj: number = Date.now();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
|
||||
/**
|
||||
* The list of HrefIndexAction type definitions
|
||||
@@ -9,6 +10,7 @@ export const HrefIndexActionTypes = {
|
||||
REMOVE_UUID: type('dspace/core/index/href/REMOVE_UUID')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
* An ngrx action to add an href to the index
|
||||
*/
|
||||
@@ -48,11 +50,11 @@ export class RemoveUUIDFromHrefIndexAction implements Action {
|
||||
constructor(uuid: string) {
|
||||
this.payload = uuid;
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all HrefIndexActions
|
||||
*/
|
||||
export type HrefIndexAction
|
||||
= AddToHrefIndexAction
|
||||
| RemoveUUIDFromHrefIndexAction;
|
||||
export type HrefIndexAction = AddToHrefIndexAction | RemoveUUIDFromHrefIndexAction;
|
||||
|
@@ -1,19 +1,16 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Effect, Actions } from "@ngrx/effects";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions } from '@ngrx/effects';
|
||||
|
||||
import {
|
||||
ObjectCacheActionTypes, AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction
|
||||
} from "../cache/object-cache.actions";
|
||||
import { AddToHrefIndexAction, RemoveUUIDFromHrefIndexAction } from "./href-index.actions";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
} from '../cache/object-cache.actions';
|
||||
import { AddToHrefIndexAction, RemoveUUIDFromHrefIndexAction } from './href-index.actions';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
@Injectable()
|
||||
export class HrefIndexEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions
|
||||
) { }
|
||||
|
||||
@Effect() add$ = this.actions$
|
||||
.ofType(ObjectCacheActionTypes.ADD)
|
||||
.filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.self))
|
||||
@@ -29,4 +26,9 @@ export class HrefIndexEffects {
|
||||
.map((action: RemoveFromObjectCacheAction) => {
|
||||
return new RemoveUUIDFromHrefIndexAction(action.payload);
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import {
|
||||
HrefIndexAction, HrefIndexActionTypes, AddToHrefIndexAction,
|
||||
HrefIndexAction,
|
||||
HrefIndexActionTypes,
|
||||
AddToHrefIndexAction,
|
||||
RemoveUUIDFromHrefIndexAction
|
||||
} from "./href-index.actions";
|
||||
} from './href-index.actions';
|
||||
|
||||
export interface HrefIndexState {
|
||||
[href: string]: string
|
||||
}
|
||||
@@ -13,11 +16,11 @@ export const hrefIndexReducer = (state = initialState, action: HrefIndexAction):
|
||||
switch (action.type) {
|
||||
|
||||
case HrefIndexActionTypes.ADD: {
|
||||
return addToHrefIndex(state, <AddToHrefIndexAction>action);
|
||||
return addToHrefIndex(state, action as AddToHrefIndexAction);
|
||||
}
|
||||
|
||||
case HrefIndexActionTypes.REMOVE_UUID: {
|
||||
return removeUUIDFromHrefIndex(state, <RemoveUUIDFromHrefIndexAction>action)
|
||||
return removeUUIDFromHrefIndex(state, action as RemoveUUIDFromHrefIndexAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -33,8 +36,8 @@ function addToHrefIndex(state: HrefIndexState, action: AddToHrefIndexAction): Hr
|
||||
}
|
||||
|
||||
function removeUUIDFromHrefIndex(state: HrefIndexState, action: RemoveUUIDFromHrefIndexAction): HrefIndexState {
|
||||
let newState = Object.create(null);
|
||||
for (let href in state) {
|
||||
const newState = Object.create(null);
|
||||
for (const href in state) {
|
||||
if (state[href] !== action.payload) {
|
||||
newState[href] = state[href];
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { HrefIndexState, hrefIndexReducer } from "./href-index.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { HrefIndexState, hrefIndexReducer } from './href-index.reducer';
|
||||
|
||||
export interface IndexState {
|
||||
href: HrefIndexState
|
||||
|
@@ -18,7 +18,7 @@ export interface Serializer<T> {
|
||||
* @param models The array of models to serialize
|
||||
* @returns An object to send to the backend
|
||||
*/
|
||||
serializeArray(models: Array<T>): any;
|
||||
serializeArray(models: T[]): any;
|
||||
|
||||
/**
|
||||
* Convert a response from the backend in to a model.
|
||||
@@ -34,5 +34,5 @@ export interface Serializer<T> {
|
||||
* @param response An object returned by the backend
|
||||
* @returns an array of models of type T
|
||||
*/
|
||||
deserializeArray(response: any): Array<T>;
|
||||
deserializeArray(response: any): T[];
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { Item } from "./item.model";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Item } from './item.model';
|
||||
|
||||
export class Bitstream extends DSpaceObject {
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Item } from "./item.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Bundle extends DSpaceObject {
|
||||
/**
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Item } from "./item.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Collection extends DSpaceObject {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description
|
||||
*/
|
||||
get introductoryText(): string {
|
||||
return this.findMetadata("dc.description");
|
||||
return this.findMetadata('dc.description');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.abstract
|
||||
*/
|
||||
get shortDescription(): string {
|
||||
return this.findMetadata("dc.description.abstract");
|
||||
return this.findMetadata('dc.description.abstract');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights
|
||||
*/
|
||||
get copyrightText(): string {
|
||||
return this.findMetadata("dc.rights");
|
||||
return this.findMetadata('dc.rights');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights.license
|
||||
*/
|
||||
get license(): string {
|
||||
return this.findMetadata("dc.rights.license");
|
||||
return this.findMetadata('dc.rights.license');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.tableofcontents
|
||||
*/
|
||||
get sidebarText(): string {
|
||||
return this.findMetadata("dc.description.tableofcontents");
|
||||
return this.findMetadata('dc.description.tableofcontents');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Collection } from "./collection.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Community extends DSpaceObject {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description
|
||||
*/
|
||||
get introductoryText(): string {
|
||||
return this.findMetadata("dc.description");
|
||||
return this.findMetadata('dc.description');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.abstract
|
||||
*/
|
||||
get shortDescription(): string {
|
||||
return this.findMetadata("dc.description.abstract");
|
||||
return this.findMetadata('dc.description.abstract');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights
|
||||
*/
|
||||
get copyrightText(): string {
|
||||
return this.findMetadata("dc.rights");
|
||||
return this.findMetadata('dc.rights');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.tableofcontents
|
||||
*/
|
||||
get sidebarText(): string {
|
||||
return this.findMetadata("dc.description.tableofcontents");
|
||||
return this.findMetadata('dc.description.tableofcontents');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,90 +1,87 @@
|
||||
import { Metadatum } from "./metadatum.model"
|
||||
import { isEmpty, isNotEmpty } from "../../shared/empty.util";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { ResourceType } from "./resource-type";
|
||||
import { Metadatum } from './metadatum.model'
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
*/
|
||||
export abstract class DSpaceObject implements CacheableObject {
|
||||
|
||||
self: string;
|
||||
self: string;
|
||||
|
||||
/**
|
||||
* The human-readable identifier of this DSpaceObject
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The human-readable identifier of this DSpaceObject
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The universally unique identifier of this DSpaceObject
|
||||
*/
|
||||
uuid: string;
|
||||
/**
|
||||
* The universally unique identifier of this DSpaceObject
|
||||
*/
|
||||
uuid: string;
|
||||
|
||||
/**
|
||||
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
||||
*/
|
||||
type: ResourceType;
|
||||
/**
|
||||
* A string representing the kind of DSpaceObject, e.g. community, item, …
|
||||
*/
|
||||
type: ResourceType;
|
||||
|
||||
/**
|
||||
* The name for this DSpaceObject
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The name for this DSpaceObject
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* An array containing all metadata of this DSpaceObject
|
||||
*/
|
||||
metadata: Array<Metadatum>;
|
||||
/**
|
||||
* An array containing all metadata of this DSpaceObject
|
||||
*/
|
||||
metadata: Metadatum[];
|
||||
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
*/
|
||||
owner: RemoteData<DSpaceObject>;
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
*/
|
||||
owner: RemoteData<DSpaceObject>;
|
||||
|
||||
/**
|
||||
* Find a metadata field by key and language
|
||||
*
|
||||
* This method returns the value of the first element
|
||||
* in the metadata array that matches the provided
|
||||
* key and language
|
||||
*
|
||||
* @param key
|
||||
* @param language
|
||||
* @return string
|
||||
*/
|
||||
findMetadata(key: string, language?: string): string {
|
||||
const metadatum = this.metadata
|
||||
.find((metadatum: Metadatum) => {
|
||||
return metadatum.key === key &&
|
||||
(isEmpty(language) || metadatum.language === language)
|
||||
});
|
||||
if (isNotEmpty(metadatum)) {
|
||||
return metadatum.value;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Find a metadata field by key and language
|
||||
*
|
||||
* This method returns the value of the first element
|
||||
* in the metadata array that matches the provided
|
||||
* key and language
|
||||
*
|
||||
* @param key
|
||||
* @param language
|
||||
* @return string
|
||||
*/
|
||||
findMetadata(key: string, language?: string): string {
|
||||
const metadatum = this.metadata.find((m: Metadatum) => {
|
||||
return m.key === key && (isEmpty(language) || m.language === language)
|
||||
});
|
||||
if (isNotEmpty(metadatum)) {
|
||||
return metadatum.value;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find metadata by an array of keys
|
||||
*
|
||||
* This method returns the values of the element
|
||||
* in the metadata array that match the provided
|
||||
* key(s)
|
||||
*
|
||||
* @param key(s)
|
||||
* @return Array<Metadatum>
|
||||
*/
|
||||
filterMetadata(keys: string[]): Metadatum[] {
|
||||
return this.metadata.filter((metadatum: Metadatum) => {
|
||||
return keys.some((key) => key === metadatum.key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find metadata by an array of keys
|
||||
*
|
||||
* This method returns the values of the element
|
||||
* in the metadata array that match the provided
|
||||
* key(s)
|
||||
*
|
||||
* @param key(s)
|
||||
* @return Array<Metadatum>
|
||||
*/
|
||||
filterMetadata(keys: string[]): Array<Metadatum> {
|
||||
return this.metadata
|
||||
.filter((metadatum: Metadatum) => {
|
||||
return keys.some(key => key === metadatum.key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user