diff --git a/.editorconfig b/.editorconfig
index f1cc3ad329..70ce43b68e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,4 @@
-# http://editorconfig.org
-
+# Editor configuration, see https://editorconfig.org
root = true
[*]
diff --git a/.gitignore b/.gitignore
index cbf74702cb..6950bea78d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,9 @@ npm-debug.log
/build/
-/config/environment.dev.js
-/config/environment.prod.js
+/src/environments/environment.ts
+/src/environments/environment.dev.ts
+/src/environments/environment.prod.ts
/coverage
@@ -36,3 +37,5 @@ yarn-error.log
package-lock.json
.java-version
+
+.env
diff --git a/.travis.yml b/.travis.yml
index 8debdcd88e..0d65d76f41 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,25 @@
sudo: required
dist: bionic
+language: node_js
+
+# Enable caching for yarn & node_modules
+cache:
+ yarn: true
+
+node_js:
+ - "10"
+ - "12"
+
+# Install latest chrome (for e2e headless testing). Run an update if needed.
+addons:
+ apt:
+ sources:
+ - google-chrome
+ packages:
+ - google-chrome-stable
+ update: true
env:
- # Install the latest docker-compose version for ci testing.
- # The default installation in travis is not compatible with the latest docker-compose file version.
- COMPOSE_VERSION: 1.24.1
# The ci step will test the dspace-angular code against DSpace REST.
# Direct that step to utilize a DSpace REST service that has been started in docker.
DSPACE_REST_HOST: localhost
@@ -12,47 +27,31 @@ env:
DSPACE_REST_NAMESPACE: '/server/api'
DSPACE_REST_SSL: false
-services:
- - xvfb
-
before_install:
- # Docker Compose Install
- - curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- - chmod +x docker-compose
- - sudo mv docker-compose /usr/local/bin
+ # Check our versions of everything
+ - echo "Check versions"
+ - yarn -v
+ - docker-compose -v
+ - google-chrome-stable --version
install:
- # update chrome
- - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
- - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
- - sudo apt-get update
- - sudo apt-get install google-chrome-stable
# Start up DSpace 7 using the entities database dump
- docker-compose -f ./docker/docker-compose-travis.yml up -d
- # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update
+ # Use the dspace-cli image to populate the assetstore. Triggers a discovery and oai update
- docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
- travis_retry yarn install
before_script:
+ - echo "Check Docker containers"
+ - docker container ls
# The following line could be enabled to verify that the rest server is responding.
- # Currently, "yarn run build" takes enough time to run to allow the service to be available
- #- curl http://localhost:8080/
-
-after_script:
- - docker-compose -f ./docker/docker-compose-travis.yml down
-
-language: node_js
-
-node_js:
- - "10"
- - "12"
-
-cache:
- yarn: true
-
-bundler_args: --retry 5
+ #- echo "Check REST API available (via Docker)"
+ #- curl http://localhost:8080/server/
script:
- - yarn run build
- yarn run ci
- - cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
+ - cat coverage/dspace-angular-cli/lcov.info | ./node_modules/coveralls/bin/coveralls.js
+
+after_script:
+ # Shutdown docker after everything runs
+ - docker-compose -f ./docker/docker-compose-travis.yml down
diff --git a/Dockerfile b/Dockerfile
index d848666175..db9983cace 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,9 +4,9 @@
FROM node:12-alpine
WORKDIR /app
ADD . /app/
-EXPOSE 3000
+EXPOSE 4000
# We run yarn install with an increased network timeout (5min) to avoid "ESOCKETTIMEDOUT" errors from hub.docker.com
# See, for example https://github.com/yarnpkg/yarn/issues/5540
RUN yarn install --network-timeout 300000
-CMD yarn run watch
+CMD yarn run start:dev
diff --git a/README.md b/README.md
index 47c040bb93..78d7816f65 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ yarn install
yarn start
```
-Then go to [http://localhost:3000](http://localhost:3000) in your browser
+Then go to [http://localhost:4000](http://localhost:4000) in your browser
Not sure where to start? watch the training videos linked in the [Introduction to the technology](#introduction-to-the-technology) section below.
@@ -59,13 +59,13 @@ Table of Contents
Introduction to the technology
------------------------------
-You can find more information on the technologies used in this project (Angular.io, Typescript, Angular Universal, RxJS, etc) on the [LYRASIS wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+UI+Technology+Stack)
+You can find more information on the technologies used in this project (Angular.io, Angular CLI, Typescript, Angular Universal, RxJS, etc) on the [LYRASIS wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+UI+Technology+Stack)
Requirements
------------
-- [Node.js](https://nodejs.org), [npm](https://www.npmjs.com/), and [yarn](https://yarnpkg.com)
-- Ensure you're running node `v10.x` or `v12.x`, npm >= `v5.x` and yarn >= `v1.x`
+- [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com)
+- Ensure you're running node `v10.x` or `v12.x` and yarn >= `v1.x`
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
@@ -77,25 +77,53 @@ Installing
### Configuring
-Default configuration file is located in `config/` folder.
+Default configuration file is located in `src/environments/` folder.
-To change the default configuration values, create local files that override the parameters you need to change:
+To change the default configuration values, create local files that override the parameters you need to change. You can use `environment.template.ts` as a starting point.
-- Create a new `environment.dev.js` file in `config/` for `devel` environment;
-- Create a new `environment.prod.js` file in `config/` for `production` environment;
+- Create a new `environment.dev.ts` file in `src/environments/` for a `development` environment;
+- Create a new `environment.prod.ts` file in `src/environments/` for a `production` environment;
-To use the configuration parameters in your component:
+The server settings can also be overwritten using an environment file.
+
+This file should be called `.env` and be placed in the project root.
+
+The following settings can be overwritten in this file:
```bash
-import { GLOBAL_CONFIG, GlobalConfig } from '../config';
+DSPACE_HOST # The host name of the angular application
+DSPACE_PORT # The port number of the angular application
+DSPACE_NAMESPACE # The namespace of the angular application
+DSPACE_SSL # Whether the angular application uses SSL [true/false]
-constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig) {}
+DSPACE_REST_HOST # The host name of the REST application
+DSPACE_REST_PORT # The port number of the REST application
+DSPACE_REST_NAMESPACE # The namespace of the REST application
+DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false]
```
+The same settings can also be overwritten by setting system environment variables instead, E.g.:
+```bash
+export DSPACE_HOST=https://dspace7.4science.cloud/server
+```
+
+The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides **`environment.(prod, dev or test).ts`** overrides **`environment.common.ts`**
+
+
+#### Using environment variables in code
+To use environment variables in a UI component, use:
+
+```typescript
+import { environment } from '../environment.ts';
+```
+
+This file is generated by the script located in `scripts/set-env.ts`. This script will run automatically before every build, or can be manually triggered using the appropriate `config` script in `package.json`
+
+
Running the app
---------------
-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`.
+After you have installed all dependencies you can now run the app. Run `yarn run start: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:4000`.
### Running in production mode
@@ -115,14 +143,6 @@ yarn run build:prod
This will build the application and put the result in the `dist` folder
-### Deploy
-```bash
-# deploy production in standalone pm2 container
-yarn run deploy
-
-# remove production from standalone pm2 container
-yarn run undeploy
-```
### Running the application with Docker
See [Docker Runtime Options](docker/README.md)
@@ -155,17 +175,15 @@ If you would like to contribute by testing a Pull Request (PR), here's how to do
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
2. `yarn run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
3. `yarn install` (Updates your local dependencies to those in the PR)
-4. `yarn start` (Rebuilds the project, and deploys to localhost:3000, by default)
-5. At this point, the code from the PR will be deployed to http://localhost:3000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
+4. `yarn start` (Rebuilds the project, and deploys to localhost:4000, by default)
+5. At this point, the code from the PR will be deployed to http://localhost:4000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
### Unit Tests
-Unit tests use Karma. You can find the configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test enviroment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
-
-To correctly run the tests you need to run the build once with: `yarn run build`.
+Unit tests use Karma. You can find the configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
The default browser is Google Chrome.
@@ -177,17 +195,13 @@ and run: `yarn run test`
E2E tests use Protractor + Selenium server + browsers. You can find the configuration file at the same level of this README file:`./protractor.conf.js` Protractor is installed as 'local' as a dev dependency.
-If you are going to use a remote test enviroment you need to edit the './protractor.conf.js'. Follow the instructions you will find inside it.
+If you are going to use a remote test enviroment you need to edit the './e2e//protractor.conf.js'. Follow the instructions you will find inside it.
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`
-
-or any command that bring up the DSpace interface.
-
Place your tests at the following path: `./e2e`
-and run: `yarn run e2e`
+and run: `ng e2e`
### Continuous Integration (CI) Test
@@ -200,7 +214,7 @@ See [`./docs`](docs) for further documentation.
### Building code documentation
-To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts informations from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
+To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts information from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
@@ -388,7 +402,7 @@ Frequently asked questions
- Where do I write my tests?
- You can write your tests next to your component files. e.g. for `src/app/home/home.component.ts` call it `src/app/home/home.component.spec.ts`
- How do I start the app when I get `EACCES` and `EADDRINUSE` errors?
- - The `EADDRINUSE` error means the port `3000` is currently being used and `EACCES` is lack of permission to build files to `./dist/`
+ - The `EADDRINUSE` error means the port `4000` is currently being used and `EACCES` is lack of permission to build files to `./dist/`
- What are the naming conventions for Angular 2?
- See [the official angular 2 style guide](https://angular.io/styleguide)
- Why is the size of my app larger in development?
diff --git a/angular.json b/angular.json
index 336738fd6e..92c0f27d2b 100644
--- a/angular.json
+++ b/angular.json
@@ -1,13 +1,157 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
- "cli": {
- "defaultCollection": "@ngrx/schematics"
- },
+ "newProjectRoot": "projects",
"projects": {
- "core": {
+ "dspace-angular-cli": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "style": "scss"
+ }
+ },
"root": "",
- "projectType": "application"
+ "sourceRoot": "src",
+ "prefix": "ds",
+ "architect": {
+ "build": {
+ "builder": "@angular-builders/custom-webpack:browser",
+ "options": {
+ "customWebpackConfig": {
+ "path": "./webpack/webpack.common.ts",
+ "mergeStrategies": {
+ "loaders": "prepend"
+ },
+ },
+ "outputPath": "dist/browser",
+ "index": "src/index.html",
+ "main": "src/main.browser.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.app.json",
+ "aot": false,
+ "assets": [
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.scss"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "optimization": true,
+ "outputHashing": "all",
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "3mb",
+ "maximumError": "5mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb",
+ "maximumError": "10kb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-builders/custom-webpack:dev-server",
+ "options": {
+ "browserTarget": "dspace-angular-cli:build",
+ "port": 4000
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "dspace-angular-cli:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "dspace-angular-cli:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-builders/custom-webpack:karma",
+ "options": {
+ "customWebpackConfig": {
+ "path": "./webpack/webpack.common.ts",
+ "mergeStrategies": {
+ "loaders": "prepend"
+ }
+ },
+ "main": "src/test.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.spec.json",
+ "karmaConfig": "karma.conf.js",
+ "assets": [
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.scss"
+ ],
+ "scripts": []
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "tsconfig.app.json",
+ "tsconfig.spec.json",
+ "e2e/tsconfig.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ },
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "dspace-angular-cli:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "dspace-angular-cli:serve:production"
+ }
+ }
+ },
+ "server": {
+ "builder": "@angular-builders/custom-webpack:server",
+ "options": {
+ "customWebpackConfig": {
+ "path": "./webpack/webpack.prod.ts",
+ "mergeStrategies": {
+ "loaders": "prepend"
+ }
+ },
+ "outputPath": "dist/server",
+ "main": "src/main.server.ts",
+ "tsConfig": "tsconfig.server.json"
+ },
+ "configurations": {
+ "production": {
+ "sourceMap": false,
+ "optimization": {
+ "scripts": false,
+ "styles": true
+ }
+ }
+ }
+ }
+ }
}
- }
-}
\ No newline at end of file
+ },
+ "defaultProject": "dspace-angular-cli"
+}
diff --git a/app.yaml b/app.yaml
deleted file mode 100644
index 248738be70..0000000000
--- a/app.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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]
diff --git a/browserslist b/browserslist
new file mode 100644
index 0000000000..f8a421c330
--- /dev/null
+++ b/browserslist
@@ -0,0 +1,11 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# You can see what browsers were selected by your queries by running:
+# npx browserslist
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not IE 9-11 # For IE 9-11 support, remove 'not'.
diff --git a/config/environment.test.js b/config/environment.test.js
deleted file mode 100644
index 6897e29aa7..0000000000
--- a/config/environment.test.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// This configuration is currently only being used for unit tests, end-to-end tests use environment.dev.ts
-module.exports = {
-
-};
diff --git a/docker/README.md b/docker/README.md
index f7b4b04848..ed0def0480 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -11,7 +11,7 @@
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
- cli.assetstore.yml
- Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
-- environment.dev.js
+- environment.dev.ts
- Environment file for running DSpace Angular in Docker
- local.cfg
- Environment file for running the DSpace 7 REST API in Docker.
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 23f0615a1f..33268778f3 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -7,7 +7,7 @@ services:
environment:
DSPACE_HOST: dspace-angular
DSPACE_NAMESPACE: /
- DSPACE_PORT: '3000'
+ DSPACE_PORT: '4000'
DSPACE_SSL: "false"
image: dspace/dspace-angular:latest
build:
@@ -16,11 +16,11 @@ services:
networks:
dspacenet:
ports:
- - published: 3000
- target: 3000
+ - published: 4000
+ target: 4000
- published: 9876
target: 9876
stdin_open: true
tty: true
volumes:
- - ./environment.dev.js:/app/config/environment.dev.js
+ - ./environment.dev.ts:/app/src/environments/environment.dev.ts
diff --git a/docker/environment.dev.js b/docker/environment.dev.ts
similarity index 70%
rename from docker/environment.dev.js
rename to docker/environment.dev.ts
index f88506012f..573c8ebb67 100644
--- a/docker/environment.dev.js
+++ b/docker/environment.dev.ts
@@ -1,11 +1,13 @@
-/*
+/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
-module.exports = {
+// This file is based on environment.template.ts provided by Angular UI
+export const environment = {
+ // Default to using the local REST API (running in Docker)
rest: {
ssl: false,
host: 'localhost',
diff --git a/docker/local.cfg b/docker/local.cfg
index 70bc45c112..a511c25789 100644
--- a/docker/local.cfg
+++ b/docker/local.cfg
@@ -1,5 +1,6 @@
dspace.dir=/dspace
db.url=jdbc:postgresql://dspacedb:5432/dspace
dspace.server.url=http://localhost:8080/server
+dspace.ui.url=http://localhost:4000
dspace.name=DSpace Started with Docker Compose
solr.server=http://dspacesolr:8983/solr
diff --git a/docs/Configuration.md b/docs/Configuration.md
index b2ec81533d..4be21d046d 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -1,23 +1,23 @@
# Configuration
-Default configuration file is located in `config/` folder. All configuration options should be listed in the default configuration file `config/environment.default.js`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change:
+Default configuration file is located in `src/environments/` folder. All configuration options should be listed in the default configuration file `src/environments/environment.common.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `environment.template.ts` as a starting point.
-- Create a new `environment.dev.js` file in `config/` for `devel` environment;
-- Create a new `environment.prod.js` file in `config/` for `production` environment;
+- Create a new `environment.dev.ts` file in `src/environments/` for `development` environment;
+- Create a new `environment.prod.ts` file in `src/environments/` for `production` environment;
Some few configuration options can be overridden by setting environment variables. These and the variable names are listed below.
## Nodejs server
-When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:3000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
+When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
```
-module.exports = {
- // Angular Universal server settings.
+export const environment = {
+ // Angular UI settings.
ui: {
ssl: false,
host: 'localhost',
- port: 3000,
+ port: 4000,
nameSpace: '/'
}
};
@@ -27,7 +27,7 @@ Alternately you can set the following environment variables. If any of these are
```
DSPACE_SSL=true
DSPACE_HOST=localhost
- DSPACE_PORT=3000
+ DSPACE_PORT=4000
DSPACE_NAMESPACE=/
```
@@ -35,14 +35,14 @@ Alternately you can set the following environment variables. If any of these are
dspace-angular connects to your DSpace installation by using its REST endpoint. To do so, you have to define the ip address, port and if ssl should be enabled. You can do this in a configuration file (see above) by adding the following options:
```
-module.exports = {
+export const environment = {
// The REST API server settings.
rest: {
ssl: true,
- host: 'dspace7.4science.it',
+ host: 'dspace7.4science.cloud',
port: 443,
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
- nameSpace: '/dspace-spring-rest/api'
+ nameSpace: '/server/api'
}
};
```
@@ -50,9 +50,9 @@ module.exports = {
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
```
DSPACE_REST_SSL=true
- DSPACE_REST_HOST=localhost
- DSPACE_REST_PORT=3000
- DSPACE_REST_NAMESPACE=/
+ DSPACE_REST_HOST=dspace7.4science.cloud
+ DSPACE_REST_PORT=443
+ DSPACE_REST_NAMESPACE=/server/api
```
## Supporting analytics services other than Google Analytics
@@ -62,4 +62,4 @@ Angulartics can be configured to work with a number of other services besides Go
In order to start using one of these services, select it from the [Angulartics Providers page](https://angulartics.github.io/angulartics2/#providers), and follow the instructions on how to configure it.
-The Google Analytics script was added in [`main.browser.ts`](https://github.com/DSpace/dspace-angular/blob/ff04760f4af91ac3e7add5e7424a46cb2439e874/src/main.browser.ts#L33) instead of the `
` tag in `index.html` to ensure events get sent when the page is shown in a client's browser, and not when it's rendered on the universal server. Likely you'll want to do the same when adding a new service.
\ No newline at end of file
+The Google Analytics script was added in [`main.browser.ts`](https://github.com/DSpace/dspace-angular/blob/ff04760f4af91ac3e7add5e7424a46cb2439e874/src/main.browser.ts#L33) instead of the `` tag in `index.html` to ensure events get sent when the page is shown in a client's browser, and not when it's rendered on the universal server. Likely you'll want to do the same when adding a new service.
diff --git a/e2e/app.po.ts b/e2e/app.po.ts
deleted file mode 100644
index 2ee9a86201..0000000000
--- a/e2e/app.po.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { browser, element, by } from 'protractor';
-
-export class ProtractorPage {
- navigateTo() {
- return browser.get('/')
- .then(() => browser.waitForAngular());
- }
-
- getPageTitleText() {
- return browser.getTitle();
- }
-
- getHomePageNewsText() {
- return element(by.css('ds-home-news')).getText();
- }
-}
diff --git a/e2e/protractor-ci.conf.js b/e2e/protractor-ci.conf.js
new file mode 100644
index 0000000000..63173e44e3
--- /dev/null
+++ b/e2e/protractor-ci.conf.js
@@ -0,0 +1,10 @@
+const config = require('./protractor.conf').config;
+
+config.capabilities = {
+ browserName: 'chrome',
+ chromeOptions: {
+ args: ['--headless', '--no-sandbox', '--disable-gpu']
+ }
+};
+
+exports.config = config;
diff --git a/protractor.conf.js b/e2e/protractor.conf.js
similarity index 94%
rename from protractor.conf.js
rename to e2e/protractor.conf.js
index 6570c9f7c3..51180c8044 100644
--- a/protractor.conf.js
+++ b/e2e/protractor.conf.js
@@ -12,10 +12,10 @@ exports.config = {
// Change to 'false' to run tests using a remote Selenium server
directConnect: true,
// Change if the website to test is not on the localhost
- baseUrl: 'http://localhost:3000/',
+ baseUrl: 'http://localhost:4000/',
// -----------------------------------------------------------------
specs: [
- './e2e/**/*.e2e-spec.ts'
+ './src/**/*.e2e-spec.ts'
],
// -----------------------------------------------------------------
// Browser and Capabilities: PhantomJS
@@ -67,7 +67,7 @@ exports.config = {
//],
plugins: [{
- path: 'node_modules/protractor-istanbul-plugin'
+ path: '../node_modules/protractor-istanbul-plugin'
}],
framework: 'jasmine',
@@ -79,7 +79,7 @@ exports.config = {
useAllAngular2AppRoots: true,
beforeLaunch: function () {
require('ts-node').register({
- project: 'e2e'
+ project: './e2e/tsconfig.json'
});
},
onPrepare: function () {
diff --git a/e2e/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts
similarity index 70%
rename from e2e/app.e2e-spec.ts
rename to e2e/src/app.e2e-spec.ts
index 995340941f..116934c1f8 100644
--- a/e2e/app.e2e-spec.ts
+++ b/e2e/src/app.e2e-spec.ts
@@ -9,11 +9,14 @@ describe('protractor App', () => {
it('should display translated title "DSpace Angular :: Home"', () => {
page.navigateTo();
+ page.waitUntilNotLoading();
expect(page.getPageTitleText()).toEqual('DSpace Angular :: Home');
});
it('should contain a news section', () => {
- page.navigateTo()
- .then(() => expect(page.getHomePageNewsText()).toBeDefined());
+ page.navigateTo();
+ page.waitUntilNotLoading();
+ const text = page.getHomePageNewsText();
+ expect(text).toBeDefined();
});
});
diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts
new file mode 100644
index 0000000000..09a7990a77
--- /dev/null
+++ b/e2e/src/app.po.ts
@@ -0,0 +1,23 @@
+import { browser, element, by, protractor, promise } from 'protractor';
+
+export class ProtractorPage {
+ navigateTo() {
+ return browser.get('/')
+ .then(() => browser.waitForAngular());
+ }
+
+ getPageTitleText() {
+ return browser.getTitle();
+ }
+
+ getHomePageNewsText() {
+ return element(by.css('ds-home-news')).getText();
+ }
+
+ waitUntilNotLoading(): promise.Promise {
+ const loading = element(by.css('.loader'))
+ const EC = protractor.ExpectedConditions;
+ const notLoading = EC.not(EC.presenceOf(loading));
+ return browser.wait(notLoading, 10000);
+ }
+}
diff --git a/e2e/pagenotfound/pagenotfound.e2e-spec.ts b/e2e/src/pagenotfound/pagenotfound.e2e-spec.ts
similarity index 100%
rename from e2e/pagenotfound/pagenotfound.e2e-spec.ts
rename to e2e/src/pagenotfound/pagenotfound.e2e-spec.ts
diff --git a/e2e/pagenotfound/pagenotfound.po.ts b/e2e/src/pagenotfound/pagenotfound.po.ts
similarity index 100%
rename from e2e/pagenotfound/pagenotfound.po.ts
rename to e2e/src/pagenotfound/pagenotfound.po.ts
diff --git a/e2e/search-navbar/search-navbar.e2e-spec.ts b/e2e/src/search-navbar/search-navbar.e2e-spec.ts
similarity index 100%
rename from e2e/search-navbar/search-navbar.e2e-spec.ts
rename to e2e/src/search-navbar/search-navbar.e2e-spec.ts
diff --git a/e2e/search-navbar/search-navbar.po.ts b/e2e/src/search-navbar/search-navbar.po.ts
similarity index 100%
rename from e2e/search-navbar/search-navbar.po.ts
rename to e2e/src/search-navbar/search-navbar.po.ts
diff --git a/e2e/search-page/search-page.e2e-spec.ts b/e2e/src/search-page/search-page.e2e-spec.ts
similarity index 72%
rename from e2e/search-page/search-page.e2e-spec.ts
rename to e2e/src/search-page/search-page.e2e-spec.ts
index e2ab6de824..af32208bc4 100644
--- a/e2e/search-page/search-page.e2e-spec.ts
+++ b/e2e/src/search-page/search-page.e2e-spec.ts
@@ -1,6 +1,5 @@
import { ProtractorPage } from './search-page.po';
import { browser } from 'protractor';
-import { promise } from 'selenium-webdriver';
describe('protractor SearchPage', () => {
let page: ProtractorPage;
@@ -23,9 +22,11 @@ describe('protractor SearchPage', () => {
.then(() => page.getRandomScopeOption())
.then((scopeString: string) => {
page.navigateToSearchWithScopeParameter(scopeString);
- page.getCurrentScope().then((s: string) => {
- expect(s).toEqual(scopeString);
- });
+ page.waitUntilNotLoading();
+ page.getCurrentScope()
+ .then((s: string) => {
+ expect(s).toEqual(scopeString);
+ })
});
});
@@ -33,13 +34,16 @@ describe('protractor SearchPage', () => {
page.navigateToSearch()
.then(() => page.getRandomScopeOption())
.then((scopeString: string) => {
- page.setCurrentScope(scopeString);
- page.submitSearchForm();
- browser.wait(() => {
- return browser.getCurrentUrl().then((url: string) => {
- return url.indexOf('scope=' + encodeURI(scopeString)) !== -1;
- });
- });
+ page.setCurrentScope(scopeString)
+ .then(() => page.submitSearchForm())
+ .then(() => page.waitUntilNotLoading())
+ .then(() => () => {
+ browser.wait(() => {
+ return browser.getCurrentUrl().then((url: string) => {
+ return url.indexOf('scope=' + encodeURI(scopeString)) !== -1;
+ })
+ })
+ })
});
});
diff --git a/e2e/search-page/search-page.po.ts b/e2e/src/search-page/search-page.po.ts
similarity index 76%
rename from e2e/search-page/search-page.po.ts
rename to e2e/src/search-page/search-page.po.ts
index 51bf86453b..83a66a848b 100644
--- a/e2e/search-page/search-page.po.ts
+++ b/e2e/src/search-page/search-page.po.ts
@@ -27,7 +27,7 @@ export class ProtractorPage {
}
setCurrentScope(scope: string) {
- element(by.css('#search-form option[value="' + scope + '"]')).click();
+ return element(by.css('#search-form option[value="' + scope + '"]')).click();
}
setCurrentQuery(query: string) {
@@ -35,7 +35,7 @@ export class ProtractorPage {
}
submitSearchForm() {
- element(by.css('#search-form button.search-button')).click();
+ return element(by.css('#search-form button.search-button')).click();
}
getRandomScopeOption(): promise.Promise {
@@ -46,4 +46,10 @@ export class ProtractorPage {
});
}
+ waitUntilNotLoading(): promise.Promise {
+ const loading = element(by.css('.loader'));
+ const EC = protractor.ExpectedConditions;
+ const notLoading = EC.not(EC.presenceOf(loading));
+ return browser.wait(notLoading, 10000);
+ }
}
diff --git a/karma.conf.js b/karma.conf.js
index f40c8b2166..9844d65904 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,176 +1,37 @@
-/**
- * @author: @AngularClass
- */
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
-
- var testWebpackConfig = require('./webpack/webpack.test.js')({
- env: 'test'
- });
-
- // Uncomment and change to run tests on a remote Selenium server
- var webdriverConfig = {
- hostname: 'localhost',
- port: 4444
- };
-
- var configuration = {
- client: {
- jasmine: {
- random: false
- }
- },
- // base path that will be used to resolve all patterns (e.g. files, exclude)
+ config.set({
basePath: '',
-
- /*
- * Frameworks to use
- *
- * available frameworks: https://npmjs.org/browse/keyword/karma-adapter
- */
- frameworks: ['jasmine'],
-
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
- require("istanbul-instrumenter-loader"),
- require('karma-chrome-launcher'),
- require('karma-coverage'),
- require("karma-istanbul-preprocessor"),
require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma'),
require('karma-mocha-reporter'),
- require('karma-phantomjs-launcher'),
- require('karma-remap-coverage'),
- require('karma-remap-istanbul'),
- require('karma-sourcemap-loader'),
- require('karma-webdriver-launcher'),
- require('karma-webpack')
],
-
- // list of files to exclude
- exclude: [],
-
- /*
- * list of files / patterns to load in the browser
- *
- * we are building the test environment in ./spec-bundle.js
- */
- 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': [
- 'istanbul',
- 'webpack',
- 'sourcemap'
- ]
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
},
-
- // Webpack Config at ./webpack.test.js
- webpack: testWebpackConfig,
-
- // save interim raw coverage report in memory
- coverageReporter: {
- type: 'in-memory'
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, './coverage/dspace-angular-cli'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
},
-
- remapCoverageReporter: {
- 'text-summary': null, // to show summary in console
- html: './coverage/html',
- cobertura: './coverage/cobertura.xml'
- },
-
- remapIstanbulReporter: {
- remapOptions: {}, //additional remap options
- reports: {
- json: './coverage/coverage.json',
- lcovonly: './coverage/lcov.info',
- html: './coverage/html/',
- }
- },
-
- /**
- * Webpack please don't spam the console when running in karma!
- */
- webpackMiddleware: {
- /**
- * webpack-dev-middleware configuration
- * i.e.
- */
- noInfo: true,
- /**
- * and use stats to turn off verbose output
- */
- stats: {
- /**
- * options i.e.
- */
- chunks: false
- }
- },
-
- /*
- * test results reporter to use
- *
- * possible values: 'dots', 'progress'
- * available reporters: https://npmjs.org/browse/keyword/karma-reporter
- */
- reporters: [
- 'mocha',
- 'coverage',
- 'remap-coverage',
- 'karma-remap-istanbul'
- ],
-
- // Karma web server port
- port: 9876,
-
- // enable / disable colors in the output (reporters and logs)
- colors: true,
-
- /*
- * level of logging
- * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
- */
- logLevel: config.LOG_WARN,
-
- // enable / disable watching file and executing tests whenever any file changes
- autoWatch: false,
-
- /*
- * start these browsers
- * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
- */
- browsers: [
- 'Chrome'
- ],
-
- customLaunchers: {
- // Remote Selenium Server with Chrome - launcher
- 'SeleniumChrome': {
- base: 'WebDriver',
- config: webdriverConfig,
- browserName: 'chrome'
- },
- // Remote Selenium Server with Firefox - launcher
- 'SeleniumFirefox': {
- base: 'WebDriver',
- config: webdriverConfig,
- browserName: 'firefox'
- }
- },
-
+ reporters: ['mocha', 'kjhtml'],
mochaReporter: {
- ignoreSkipped: true
+ ignoreSkipped: true,
+ output: 'autowatch'
},
-
- browserNoActivityTimeout: 30000
-
- };
-
- config.set(configuration);
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
};
diff --git a/mock-nodemon.json b/mock-nodemon.json
new file mode 100644
index 0000000000..18fc86bd9d
--- /dev/null
+++ b/mock-nodemon.json
@@ -0,0 +1,5 @@
+{
+ "watch": ["src/environments/mock-environment.ts"],
+ "ext": "ts",
+ "exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts"
+}
diff --git a/nodemon.json b/nodemon.json
index 107ae1a754..39e9d9aa5b 100644
--- a/nodemon.json
+++ b/nodemon.json
@@ -1,9 +1,6 @@
{
- "watch": [
- "dist",
- "config",
- "src/index.html"
- ],
- "ext": "js ts json html",
- "delay": "50"
+ "watch": ["src/environments"],
+ "ext": "ts",
+ "ignore": ["src/environments/environment.ts", "src/environments/mock-environment.ts"],
+ "exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev"
}
diff --git a/package.json b/package.json
index 0f3fa03c1e..8d3c936212 100644
--- a/package.json
+++ b/package.json
@@ -1,21 +1,38 @@
{
- "name": "dspace-angular",
- "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": "10.* || >= 12.*"
- },
- "resolutions": {
- "serialize-javascript": ">= 2.1.2",
- "set-value": ">= 2.0.1"
- },
+ "name": "dspace-angular-cli",
+ "version": "0.0.0",
"scripts": {
- "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",
+ "ng": "ng",
+ "config:dev": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev",
+ "config:prod": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --prod",
+ "config:test": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts",
+ "config:test:watch": "nodemon --config mock-nodemon.json",
+ "config:dev:watch": "nodemon",
+ "prestart:dev": "yarn run config:dev",
+ "prebuild": "yarn run config:dev",
+ "pretest": "yarn run config:test",
+ "pretest:watch": "yarn run config:test",
+ "pretest:headless": "yarn run config:test",
+ "prebuild:prod": "yarn run config:prod",
+ "pree2e": "yarn run config:prod",
+ "pree2e:ci": "yarn run config:prod",
+ "start": "yarn run start:prod",
+ "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
+ "start:dev": "npm-run-all --parallel config:dev:watch serve",
+ "start:prod": "yarn run build:prod && yarn run serve:ssr",
+ "build": "ng build",
+ "build:prod": "yarn run build:ssr",
+ "build:ssr": "yarn run build:client-and-server-bundles && yarn run compile:server",
+ "build:client-and-server-bundles": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build --prod && ng run dspace-angular-cli:server:production --bundleDependencies all",
+ "test:watch": "npm-run-all --parallel config:test:watch test",
+ "test": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng test --sourceMap=true --watch=true",
+ "test:headless": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage",
+ "lint": "ng lint",
+ "e2e": "ng e2e",
+ "e2e:ci": "ng e2e --protractor-config=./e2e/protractor-ci.conf.js",
+ "compile:server": "webpack --config webpack.server.config.js --progress --colors",
+ "serve:ssr": "node dist/server",
+ "ci": "ng lint && yarn run build:prod && yarn test:headless && yarn run e2e:ci",
"clean:coverage": "rimraf coverage",
"clean:dist": "rimraf dist",
"clean:doc": "rimraf doc",
@@ -24,105 +41,64 @@
"clean:bld": "rimraf build",
"clean:node": "rimraf node_modules",
"clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld",
- "clean": "yarn run clean:prod && yarn run clean:node",
- "prebuild": "yarn run clean:bld && yarn run clean:dist",
- "prebuild:ci": "yarn run prebuild",
- "prebuild:prod": "yarn run prebuild",
- "build": "node ./scripts/webpack.js --progress --mode development",
- "build:ci": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development",
- "build:prod": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode production && node ./scripts/webpack.js --env.aot --env.client --mode production",
- "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 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",
- "syncbuilddir": "node ./scripts/sync-build-dir.js",
- "webpack:watch": "node ./scripts/webpack.js -w --mode development",
- "watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
- "watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
- "predebug": "yarn run build",
- "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 --mode development",
- "debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production",
- "ci": "yarn run lint && yarn run build:ci && yarn run test:headless && npm-run-all -p -r server e2e",
- "protractor": "node node_modules/protractor/bin/protractor",
- "pree2e": "yarn run webdriver:update",
- "e2e": "yarn run protractor",
- "pretest": "yarn run clean:bld",
- "pretest:headless": "yarn run pretest",
- "pretest:watch": "yarn run pretest",
- "test": "karma start --single-run",
- "test:headless": "karma start --single-run --browsers ChromeHeadless",
- "test:watch": "karma start --no-single-run --auto-watch",
- "webdriver:start": "node node_modules/protractor/bin/webdriver-manager start --seleniumPort 4444",
- "webdriver:update": "node node_modules/protractor/bin/webdriver-manager update --standalone --gecko false",
- "lint": "tslint \"src/**/*.ts\" && tslint \"e2e/**/*.ts\"",
- "docs": "typedoc --options typedoc.json ./src/",
- "coverage": "http-server -c-1 -o -p 9875 ./coverage",
- "postinstall": "yarn run patch-protractor",
- "patch-protractor": "ncp node_modules/webdriver-manager node_modules/protractor/node_modules/webdriver-manager",
- "sync-i18n": "node ./scripts/sync-i18n-files.js"
+ "clean": "yarn run clean:prod && yarn run clean:node && yarn run clean:env",
+ "clean:env": "rimraf src/environments/environment.ts",
+ "sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts"
+ },
+ "browser": {
+ "fs": false,
+ "path": false,
+ "http": false,
+ "https": false
+ },
+ "private": true,
+ "resolutions": {
+ "minimist": "^1.2.5"
},
"dependencies": {
- "@angular/animations": "^8.2.14",
+ "@angular/animations": "~8.2.14",
"@angular/cdk": "8.2.3",
- "@angular/cli": "^8.3.25",
- "@angular/common": "^8.2.14",
- "@angular/core": "^8.2.14",
- "@angular/forms": "^8.2.14",
- "@angular/platform-browser": "^8.2.14",
- "@angular/platform-browser-dynamic": "^8.2.14",
- "@angular/platform-server": "^8.2.14",
- "@angular/router": "^8.2.14",
+ "@angular/common": "~8.2.14",
+ "@angular/compiler": "~8.2.14",
+ "@angular/core": "~8.2.14",
+ "@angular/forms": "~8.2.14",
+ "@angular/platform-browser": "~8.2.14",
+ "@angular/platform-browser-dynamic": "~8.2.14",
+ "@angular/platform-server": "~8.2.14",
+ "@angular/router": "~8.2.14",
"@angularclass/bootloader": "1.0.1",
- "@ng-bootstrap/ng-bootstrap": "^5.2.1",
+ "@ng-bootstrap/ng-bootstrap": "5.2.1",
"@ng-dynamic-forms/core": "8.1.1",
"@ng-dynamic-forms/ui-ng-bootstrap": "8.1.1",
"@ngrx/effects": "^8.6.0",
"@ngrx/router-store": "^8.6.0",
"@ngrx/store": "^8.6.0",
- "@nguniversal/express-engine": "^8.2.6",
+ "@nguniversal/express-engine": "8.2.6",
+ "@nguniversal/module-map-ngfactory-loader": "v8.2.6",
"@ngx-translate/core": "11.0.1",
- "@ngx-translate/http-loader": "4.0.0",
"@nicky-lenaers/ngx-scroll-to": "^3.0.1",
"angular-idle-preload": "3.0.0",
"angular2-text-mask": "9.0.0",
"angulartics2": "7.5.2",
- "body-parser": "1.18.2",
"bootstrap": "4.3.1",
+ "caniuse-lite": "^1.0.30000697",
"cerialize": "0.1.18",
- "compression": "1.7.1",
+ "cli-progress": "^3.8.0",
"cookie-parser": "1.4.3",
"core-js": "^3.6.4",
"debug-loader": "^0.0.1",
+ "deepmerge": "^4.2.2",
"express": "4.16.2",
- "express-session": "1.15.6",
"fast-json-patch": "^2.0.7",
"file-saver": "^1.3.8",
+ "filesize": "^6.1.0",
"font-awesome": "4.7.0",
- "fork-ts-checker-webpack-plugin": "^0.4.10",
- "hammerjs": "^2.0.8",
- "http-server": "0.11.1",
"https": "1.0.0",
"js-cookie": "2.2.0",
- "js.clone": "0.0.3",
"json5": "^2.1.0",
"jsonschema": "1.2.2",
"jwt-decode": "^2.2.0",
- "methods": "1.1.2",
"moment": "^2.22.1",
- "moment-range": "^4.0.2",
"morgan": "^1.9.1",
"ng-mocks": "^8.1.0",
"ng2-file-upload": "1.2.1",
@@ -134,124 +110,74 @@
"ngx-sortablejs": "^3.1.4",
"nouislider": "^11.0.0",
"pem": "1.13.2",
+ "postcss-cli": "^6.0.0",
"reflect-metadata": "^0.1.13",
- "rxjs": "6.5.4",
+ "rxjs": "~6.4.0",
"rxjs-spy": "^7.5.1",
"sass-resources-loader": "^2.0.0",
"sortablejs": "1.7.0",
- "text-mask-core": "5.0.1",
- "ts-loader": "^5.2.1",
- "ts-md5": "^1.2.4",
- "url-parse": "^1.4.7",
- "uuid": "^3.2.1",
+ "tslib": "^1.10.0",
"webfontloader": "1.6.28",
- "webpack-cli": "^3.2.0",
"zone.js": "^0.9.1"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^0.803.25",
- "@angular/compiler": "^8.2.14",
- "@angular/compiler-cli": "^8.2.14",
+ "@angular-builders/custom-webpack": "8.4.1",
+ "@angular-devkit/build-angular": "~0.803.25",
+ "@angular/cli": "~8.3.25",
+ "@angular/compiler-cli": "~8.2.14",
+ "@angular/language-service": "~8.2.14",
"@fortawesome/fontawesome-free": "^5.5.0",
- "@ngrx/entity": "^8.6.0",
- "@ngrx/schematics": "^8.6.0",
"@ngrx/store-devtools": "^8.6.0",
"@ngtools/webpack": "^8.3.25",
- "@schematics/angular": "^0.7.5",
- "@types/acorn": "^4.0.3",
- "@types/cookie-parser": "1.4.1",
"@types/deep-freeze": "0.1.1",
- "@types/express": "^4.11.1",
- "@types/express-serve-static-core": "4.16.0",
+ "@types/express": "^4.17.0",
"@types/file-saver": "^1.3.0",
- "@types/hammerjs": "2.0.35",
"@types/jasmine": "^3.3.9",
+ "@types/jasminewd2": "~2.0.3",
"@types/js-cookie": "2.1.0",
- "@types/json5": "^0.0.30",
"@types/lodash": "^4.14.110",
- "@types/memory-cache": "0.2.0",
- "@types/mime": "2.0.0",
- "@types/node": "^11.11.2",
- "@types/serve-static": "1.13.2",
- "@types/uuid": "^3.4.3",
- "@types/webfontloader": "1.6.29",
- "@typescript-eslint/eslint-plugin": "^2.12.0",
- "@typescript-eslint/parser": "^2.12.0",
- "ajv": "^6.1.1",
- "ajv-keywords": "^3.1.0",
- "angular2-template-loader": "0.6.2",
- "autoprefixer": "^9.1.3",
- "caniuse-lite": "^1.0.30000697",
- "cli-progress": "^3.3.1",
- "codelyzer": "^5.1.0",
- "commander": "^3.0.2",
+ "@types/node": "11.15.3",
+ "codelyzer": "^5.0.0",
"compression-webpack-plugin": "^3.0.1",
"copy-webpack-plugin": "^5.1.1",
- "copyfiles": "^2.1.1",
"coveralls": "3.0.0",
"css-loader": "3.4.0",
"cssnano": "^4.1.10",
"deep-freeze": "0.0.1",
- "eslint": "^6.7.2",
- "exports-loader": "^0.7.0",
- "html-webpack-plugin": "3.2.0",
- "imports-loader": "0.8.0",
- "istanbul-instrumenter-loader": "3.0.1",
+ "dotenv": "^8.2.0",
+ "fork-ts-checker-webpack-plugin": "^0.4.10",
+ "html-webpack-plugin": "^3.2.0",
"jasmine-core": "^3.3.0",
"jasmine-marbles": "0.3.1",
- "jasmine-spec-reporter": "4.2.1",
- "karma": "4.0.1",
- "karma-chrome-launcher": "2.2.0",
- "karma-cli": "2.0.0",
- "karma-coverage": "1.1.2",
- "karma-istanbul-preprocessor": "0.0.2",
+ "jasmine-spec-reporter": "~4.2.1",
+ "karma": "^5.0.9",
+ "karma-chrome-launcher": "~2.2.0",
+ "karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "2.0.1",
+ "karma-jasmine-html-reporter": "^1.4.0",
"karma-mocha-reporter": "2.2.5",
- "karma-phantomjs-launcher": "1.0.4",
- "karma-remap-coverage": "^0.1.5",
- "karma-remap-istanbul": "0.6.0",
- "karma-sourcemap-loader": "0.3.7",
- "karma-webdriver-launcher": "^1.0.7",
- "karma-webpack": "3.0.0",
- "ncp": "^2.0.0",
- "nodemon": "^1.15.0",
- "npm-run-all": "4.1.3",
+ "nodemon": "^2.0.2",
+ "npm-run-all": "^4.1.5",
"optimize-css-assets-webpack-plugin": "^5.0.1",
- "postcss": "^7.0.2",
"postcss-apply": "0.11.0",
- "postcss-cli": "^6.0.0",
"postcss-cssnext": "3.1.0",
+ "postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-responsive-type": "1.0.0",
- "postcss-smart-import": "0.7.6",
- "protractor": "^5.4.2",
+ "protractor": "^7.0.0",
"protractor-istanbul-plugin": "2.0.0",
"raw-loader": "0.5.1",
- "rimraf": "2.6.2",
- "rollup": "^0.65.0",
- "rollup-plugin-commonjs": "^9.1.6",
- "rollup-plugin-node-globals": "1.2.1",
- "rollup-plugin-node-resolve": "^3.0.3",
- "rollup-plugin-terser": "^2.0.2",
- "sass-loader": "7.3.1",
+ "rimraf": "^3.0.2",
"script-ext-html-webpack-plugin": "2.1.4",
- "source-map": "0.7.3",
- "source-map-loader": "0.2.4",
"string-replace-loader": "^2.1.1",
"terser-webpack-plugin": "^2.3.1",
- "to-string-loader": "1.1.5",
- "ts-helpers": "1.1.2",
- "ts-node": "4.1.0",
- "tslint": "5.11.0",
- "typedoc": "^0.9.0",
- "typescript": "3.5.3",
- "webdriver-manager": "^12.1.7",
- "webpack": "^4.29.6",
+ "ts-loader": "^5.2.0",
+ "ts-node": "^8.8.1",
+ "tslint": "~5.15.0",
+ "typescript": "~3.5.3",
+ "webpack": "^4.0.0",
"webpack-bundle-analyzer": "^3.3.2",
- "webpack-dev-middleware": "3.2.0",
- "webpack-dev-server": "^3.1.11",
- "webpack-import-glob-loader": "^1.6.3",
- "webpack-merge": "4.1.4",
+ "webpack-cli": "^3.1.0",
"webpack-node-externals": "1.7.2"
}
}
diff --git a/postcss.config.js b/postcss.config.js
index c499f9da90..1c46e245ea 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,6 +1,6 @@
module.exports = {
plugins: [
- require('postcss-smart-import')(),
+ require('postcss-import')(),
require('postcss-cssnext')(),
require('postcss-apply')(),
require('postcss-responsive-type')()
diff --git a/rollup.config.js b/rollup.config.js
deleted file mode 100644
index 33e3ec3346..0000000000
--- a/rollup.config.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import nodeResolve from 'rollup-plugin-node-resolve'
-import commonjs from 'rollup-plugin-commonjs';
-import terser from 'rollup-plugin-terser'
-
-export default {
- input: 'dist/client.js',
- output: {
- file: 'dist/client.js',
- format: 'iife',
- },
- plugins: [
- nodeResolve({
- jsnext: true,
- module: true
- }),
- commonjs({
- include: 'node_modules/rxjs/**'
- }),
- terser.terser()
- ]
-}
diff --git a/scripts/serve.ts b/scripts/serve.ts
new file mode 100644
index 0000000000..c69f8e8a21
--- /dev/null
+++ b/scripts/serve.ts
@@ -0,0 +1,11 @@
+import { environment } from '../src/environments/environment';
+
+import * as child from 'child_process';
+
+/**
+ * Calls `ng serve` with the following arguments configured for the UI in the environment file: host, port, nameSpace, ssl
+ */
+child.spawn(
+ `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`,
+ { stdio:'inherit', shell: true }
+);
diff --git a/scripts/set-env.ts b/scripts/set-env.ts
new file mode 100644
index 0000000000..5eee22a4be
--- /dev/null
+++ b/scripts/set-env.ts
@@ -0,0 +1,116 @@
+import { writeFile } from 'fs';
+import { environment as commonEnv } from '../src/environments/environment.common';
+import { GlobalConfig } from '../src/config/global-config.interface';
+import { ServerConfig } from '../src/config/server-config.interface';
+import { hasValue } from '../src/app/shared/empty.util';
+
+// Configure Angular `environment.ts` file path
+const targetPath = './src/environments/environment.ts';
+// Load node modules
+const colors = require('colors');
+require('dotenv').config();
+const merge = require('deepmerge');
+
+const environment = process.argv[2];
+let environmentFilePath;
+let production = false;
+
+switch (environment) {
+ case '--prod':
+ case '--production':
+ production = true;
+ console.log(`Building ${colors.red.bold(`production`)} environment`);
+ environmentFilePath = '../src/environments/environment.prod.ts';
+ break;
+ case '--test':
+ console.log(`Building ${colors.blue.bold(`test`)} environment`);
+ environmentFilePath = '../src/environments/environment.test.ts';
+ break;
+ default:
+ console.log(`Building ${colors.green.bold(`development`)} environment`);
+ environmentFilePath = '../src/environments/environment.dev.ts';
+}
+
+const processEnv = {
+ ui: createServerConfig(
+ process.env.DSPACE_HOST,
+ process.env.DSPACE_PORT,
+ process.env.DSPACE_NAMESPACE,
+ process.env.DSPACE_SSL),
+ rest: createServerConfig(
+ process.env.DSPACE_REST_HOST,
+ process.env.DSPACE_REST_PORT,
+ process.env.DSPACE_REST_NAMESPACE,
+ process.env.DSPACE_REST_SSL)
+} as GlobalConfig;
+
+import(environmentFilePath)
+ .then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv])))
+ .catch(() => {
+ console.log(colors.yellow.bold(`No specific environment file found for ` + environment));
+ generateEnvironmentFile(merge(commonEnv, processEnv))
+ });
+
+function generateEnvironmentFile(file: GlobalConfig): void {
+ file.production = production;
+ buildBaseUrls(file);
+ const contents = `export const environment = ` + JSON.stringify(file);
+ writeFile(targetPath, contents, (err) => {
+ if (err) {
+ throw console.error(err);
+ } else {
+ console.log(`Angular ${colors.bold('environment.ts')} file generated correctly at ${colors.bold(targetPath)} \n`);
+ }
+ });
+}
+
+// allow to override a few important options by environment variables
+function createServerConfig(host?: string, port?: string, nameSpace?: string, ssl?: string): ServerConfig {
+ const result = {} as any;
+ if (hasValue(host)) {
+ result.host = host;
+ }
+
+ if (hasValue(nameSpace)) {
+ result.nameSpace = nameSpace;
+ }
+
+ if (hasValue(port)) {
+ result.port = Number(port);
+ }
+
+ if (hasValue(ssl)) {
+ result.ssl = ssl.trim().match(/^(true|1|yes)$/i) ? true : false;
+ }
+
+ return result;
+}
+
+function buildBaseUrls(config: GlobalConfig): void {
+ for (const key in config) {
+ if (config.hasOwnProperty(key) && config[key].host) {
+ config[key].baseUrl = [
+ getProtocol(config[key].ssl),
+ getHost(config[key].host),
+ getPort(config[key].port),
+ getNameSpace(config[key].nameSpace)
+ ].join('');
+ }
+ }
+}
+
+function getProtocol(ssl: boolean): string {
+ return ssl ? 'https://' : 'http://';
+}
+
+function getHost(host: string): string {
+ return host;
+}
+
+function getPort(port: number): string {
+ return port ? (port !== 80 && port !== 443) ? ':' + port : '' : '';
+}
+
+function getNameSpace(nameSpace: string): string {
+ return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : '';
+}
diff --git a/scripts/set-mock-env.ts b/scripts/set-mock-env.ts
new file mode 100644
index 0000000000..5271432896
--- /dev/null
+++ b/scripts/set-mock-env.ts
@@ -0,0 +1,11 @@
+import { copyFile } from 'fs';
+
+// Configure Angular `environment.ts` file path
+const sourcePath = './src/environments/mock-environment.ts';
+const targetPath = './src/environments/environment.ts';
+
+// destination.txt will be created or overwritten by default.
+copyFile(sourcePath, targetPath, (err) => {
+ if (err) throw err;
+ console.log(sourcePath + ' was copied to ' + targetPath);
+});
diff --git a/scripts/sync-i18n-files.js b/scripts/sync-i18n-files.ts
similarity index 98%
rename from scripts/sync-i18n-files.js
rename to scripts/sync-i18n-files.ts
index b1e8062a67..ad8a712f21 100755
--- a/scripts/sync-i18n-files.js
+++ b/scripts/sync-i18n-files.ts
@@ -1,10 +1,9 @@
-#!/usr/bin/env node
+import { projectRoot} from '../webpack/helpers';
const commander = require('commander');
const fs = require('fs');
const JSON5 = require('json5');
const _cliProgress = require('cli-progress');
const _ = require('lodash');
-const {projectRoot} = require('../webpack/helpers');
const program = new commander.Command();
program.version('1.0.0', '-v, --version');
@@ -13,8 +12,8 @@ const NEW_MESSAGE_TODO = '// TODO New key - Add a translation';
const MESSAGE_CHANGED_TODO = '// TODO Source message changed - Revise the translation';
const COMMENTS_CHANGED_TODO = '// TODO Source comments changed - Revise the translation';
-const DEFAULT_SOURCE_FILE_LOCATION = 'resources/i18n/en.json5';
-const LANGUAGE_FILES_LOCATION = 'resources/i18n';
+const DEFAULT_SOURCE_FILE_LOCATION = 'src/assets/i18n/en.json5';
+const LANGUAGE_FILES_LOCATION = 'src/assets/i18n';
parseCliInput();
diff --git a/server.ts b/server.ts
new file mode 100644
index 0000000000..a5d47d8bd7
--- /dev/null
+++ b/server.ts
@@ -0,0 +1,246 @@
+/**
+ * *** NOTE ON IMPORTING FROM ANGULAR AND NGUNIVERSAL IN THIS FILE ***
+ *
+ * If your application uses third-party dependencies, you'll need to
+ * either use Webpack or the Angular CLI's `bundleDependencies` feature
+ * in order to adequately package them for use on the server without a
+ * node_modules directory.
+ *
+ * However, due to the nature of the CLI's `bundleDependencies`, importing
+ * Angular in this file will create a different instance of Angular than
+ * the version in the compiled application code. This leads to unavoidable
+ * conflicts. Therefore, please do not explicitly import from @angular or
+ * @nguniversal in this file. You can export any needed resources
+ * from your application's main.server.ts file, as seen below with the
+ * import for `ngExpressEngine`.
+ */
+
+import 'zone.js/dist/zone-node';
+import 'reflect-metadata';
+import 'rxjs';
+
+import * as fs from 'fs';
+import * as pem from 'pem';
+import * as https from 'https';
+import * as morgan from 'morgan';
+import * as express from 'express';
+import * as bodyParser from 'body-parser';
+import * as compression from 'compression';
+import * as cookieParser from 'cookie-parser';
+import { join } from 'path';
+
+import { enableProdMode, NgModuleFactory, Type } from '@angular/core';
+
+import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
+import { environment } from './src/environments/environment';
+
+/*
+ * Set path for the browser application's dist folder
+ */
+const DIST_FOLDER = join(process.cwd(), 'dist/browser');
+
+// * NOTE :: leave this as require() since this file is built Dynamically from webpack
+const { ServerAppModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main');
+
+/*
+ * Create a new express application
+ */
+const app = express();
+
+/*
+ * If production mode is enabled in the environment file:
+ * - Enable Angular's production mode
+ * - Enable compression for response bodies. See [compression](https://github.com/expressjs/compression)
+ */
+if (environment.production) {
+ enableProdMode();
+ app.use(compression());
+}
+
+/*
+ * Enable request logging
+ * See [morgan](https://github.com/expressjs/morgan)
+ */
+app.use(morgan('dev'));
+
+/*
+ * Add cookie parser middleware
+ * See [morgan](https://github.com/expressjs/cookie-parser)
+ */
+app.use(cookieParser());
+
+/*
+ * Add parser for request bodies
+ * See [morgan](https://github.com/expressjs/body-parser)
+ */
+app.use(bodyParser.json());
+
+/*
+ * Render html pages by running angular server side
+ */
+app.engine('html', (_, options, callback) =>
+ ngExpressEngine({
+ bootstrap: ServerAppModuleNgFactory,
+ providers: [
+ {
+ provide: REQUEST,
+ useValue: (options as any).req,
+ },
+ {
+ provide: RESPONSE,
+ useValue: (options as any).req.res,
+ },
+ provideModuleMap(LAZY_MODULE_MAP)
+ ],
+ })(_, (options as any), callback)
+);
+
+/*
+ * Register the view engines for html and ejs
+ */
+app.set('view engine', 'ejs');
+app.set('view engine', 'html');
+
+/*
+ * Set views folder path to directory where template files are stored
+ */
+app.set('views', DIST_FOLDER);
+
+/*
+ * Adds a cache control header to the response
+ * The cache control value can be configured in the environments file and defaults to max-age=60
+ */
+function cacheControl(req, res, next) {
+ // instruct browser to revalidate
+ res.header('Cache-Control', environment.cache.control || 'max-age=60');
+ next();
+}
+
+/*
+ * Serve static resources (images, i18n messages, …)
+ */
+app.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false }));
+
+/*
+ * The callback function to serve server side angular
+ */
+function ngApp(req, res) {
+ // Object to be set to window.dspace when CSR is used
+ // this allows us to pass the info in the original request
+ // to the dspace7-angular instance running in the client's browser
+ const dspace = {
+ originalRequest: {
+ headers: req.headers,
+ body: req.body,
+ method: req.method,
+ params: req.params,
+ reportProgress: req.reportProgress,
+ withCredentials: req.withCredentials,
+ responseType: req.responseType,
+ urlWithParams: req.urlWithParams
+ }
+ };
+
+ // callback function for the case when SSR throws an error.
+ function onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
+ if (!res._headerSent) {
+ console.warn('Error in SSR, serving for direct CSR. Error details : ', error);
+ res.sendFile('index.csr.ejs', {
+ root: DIST_FOLDER,
+ scripts: ``
+ });
+ }
+ }
+
+ if (environment.universal.preboot) {
+ // If preboot is enabled, create a new zone for SSR, and
+ // register the error handler for when it throws an error
+ Zone.current.fork({ name: 'CSR fallback', onHandleError }).run(() => {
+ res.render(DIST_FOLDER + '/index.html', {
+ req,
+ res,
+ preboot: environment.universal.preboot,
+ async: environment.universal.async,
+ time: environment.universal.time,
+ baseUrl: environment.ui.nameSpace,
+ originUrl: environment.ui.baseUrl,
+ requestUrl: req.originalUrl
+ });
+ });
+ } else {
+ // If preboot is disabled, just serve the client side ejs template and pass it the required
+ // variables
+ console.log('Universal off, serving for direct CSR');
+ res.render('index-csr.ejs', {
+ root: DIST_FOLDER,
+ scripts: ``
+ });
+ }
+}
+
+// Register the ngApp callback function to handle incoming requests
+app.get('*', ngApp);
+
+/*
+ * Callback function for when the server has started
+ */
+function serverStarted() {
+ console.log(`[${new Date().toTimeString()}] Listening at ${environment.ui.baseUrl}`);
+}
+
+/*
+ * Create an HTTPS server with the configured port and host
+ * @param keys SSL credentials
+ */
+function createHttpsServer(keys) {
+ https.createServer({
+ key: keys.serviceKey,
+ cert: keys.certificate
+ }, app).listen(environment.ui.port, environment.ui.host, () => {
+ serverStarted();
+ });
+}
+
+/*
+ * If SSL is enabled
+ * - Read credentials from configuration files
+ * - Call script to start an HTTPS server with these credentials
+ * When SSL is disabled
+ * - Start an HTTP server on the configured port and host
+ */
+if (environment.ui.ssl) {
+ let serviceKey;
+ try {
+ serviceKey = fs.readFileSync('./config/ssl/key.pem');
+ } catch (e) {
+ console.warn('Service key not found at ./config/ssl/key.pem');
+ }
+
+ let certificate;
+ try {
+ certificate = fs.readFileSync('./config/ssl/cert.pem');
+ } catch (e) {
+ console.warn('Certificate not found at ./config/ssl/key.pem');
+ }
+
+ if (serviceKey && certificate) {
+ createHttpsServer({
+ serviceKey: serviceKey,
+ certificate: certificate
+ });
+ } else {
+
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+
+ pem.createCertificate({
+ days: 1,
+ selfSigned: true
+ }, (error, keys) => {
+ createHttpsServer(keys);
+ });
+ }
+} else {
+ app.listen(environment.ui.port, environment.ui.host, () => {
+ serverStarted();
+ });
+}
diff --git a/spec-bundle.js b/spec-bundle.js
deleted file mode 100644
index d7f32eb20c..0000000000
--- a/spec-bundle.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @author: @AngularClass
- */
-
-/*
- * When testing with webpack and ES6, we have to do some extra
- * things to get testing to work right. Because we are gonna write tests
- * in ES6 too, we have to compile those as well. That's handled in
- * karma.conf.js with the karma-webpack plugin. This is the entry
- * file for webpack test. Just like webpack will create a bundle.js
- * file for our client, when we run test, it will compile and bundle them
- * all here! Crazy huh. So we need to do some setup
- */
-Error.stackTraceLimit = Infinity;
-
-require('core-js/es');
-require('core-js/features/reflect');
-
-// Typescript emit helpers polyfill
-require('ts-helpers');
-
-require('zone.js/dist/zone');
-require('zone.js/dist/long-stack-trace-zone');
-require('zone.js/dist/proxy'); // since zone.js 0.6.15
-require('zone.js/dist/sync-test');
-require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14
-require('zone.js/dist/async-test');
-require('zone.js/dist/fake-async-test');
-
-// RxJS
-require('rxjs');
-
-var testing = require('@angular/core/testing');
-var browser = require('@angular/platform-browser-dynamic/testing');
-
-testing.TestBed.initTestEnvironment(
- browser.BrowserDynamicTestingModule,
- browser.platformBrowserDynamicTesting()
-);
-
-var tests = require.context('./src', true, /\.spec\.ts$/);
-
-tests.keys().forEach(tests);
-
-// includes all modules into test coverage
-const modules = require.context('./src/app', true, /\.module\.ts$/);
-
-modules.keys().forEach(modules);
diff --git a/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts b/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts
index 5af18c778f..f61a3c2f71 100644
--- a/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts
+++ b/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts
@@ -6,7 +6,7 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon
import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { getAccessControlModulePath } from '../admin-routing.module';
-const GROUP_EDIT_PATH = 'groups';
+export const GROUP_EDIT_PATH = 'groups';
export function getGroupEditPath(id: string) {
return new URLCombiner(getAccessControlModulePath(), GROUP_EDIT_PATH, id).toString();
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts
index 268ab602f1..17d8655bdd 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts
+++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts
@@ -15,15 +15,15 @@ import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
import { EPerson } from '../../../core/eperson/models/eperson.model';
import { PageInfo } from '../../../core/shared/page-info.model';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
-import { getMockFormBuilderService } from '../../../shared/mocks/mock-form-builder-service';
-import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
-import { getMockTranslateService } from '../../../shared/mocks/mock-translate.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson-mock';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
-import { RouterStub } from '../../../shared/testing/router-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
import { EPeopleRegistryComponent } from './epeople-registry.component';
+import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { RouterStub } from '../../../shared/testing/router.stub';
describe('EPeopleRegistryComponent', () => {
let component: EPeopleRegistryComponent;
@@ -89,7 +89,7 @@ describe('EPeopleRegistryComponent', () => {
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts
index 49f9a9cd93..87ab70a942 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts
+++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts
@@ -1,6 +1,6 @@
-import { EPersonMock } from '../../../shared/testing/eperson-mock';
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from './epeople-registry.actions';
import { ePeopleRegistryReducer, EPeopleRegistryState } from './epeople-registry.reducers';
+import { EPersonMock } from '../../../shared/testing/eperson.mock';
const initialState: EPeopleRegistryState = {
editEPerson: null,
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html
index 578862b561..b87b3e0848 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html
+++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html
@@ -14,6 +14,18 @@
[formLayout]="formLayout"
(cancel)="onCancel()"
(submitForm)="onSubmit()">
+
+
+
+
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
index 4319c77e8b..693f3cf916 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
+++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
@@ -22,15 +22,17 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service
import { PageInfo } from '../../../../core/shared/page-info.model';
import { UUIDService } from '../../../../core/shared/uuid.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { getMockFormBuilderService } from '../../../../shared/mocks/mock-form-builder-service';
-import { getMockTranslateService } from '../../../../shared/mocks/mock-translate.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson-mock';
-import { MockTranslateLoader } from '../../../../shared/testing/mock-translate-loader';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { EPeopleRegistryComponent } from '../epeople-registry.component';
import { EPersonFormComponent } from './eperson-form.component';
+import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
+import { AuthService } from '../../../../core/auth/auth.service';
+import { AuthServiceStub } from '../../../../shared/testing/auth-service.stub';
describe('EPersonFormComponent', () => {
let component: EPersonFormComponent;
@@ -40,6 +42,7 @@ describe('EPersonFormComponent', () => {
let mockEPeople;
let ePersonDataServiceStub: any;
+ let authService: AuthServiceStub;
beforeEach(async(() => {
mockEPeople = [EPersonMock, EPersonMock2];
@@ -104,12 +107,13 @@ describe('EPersonFormComponent', () => {
};
builderService = getMockFormBuilderService();
translateService = getMockTranslateService();
+ authService = new AuthServiceStub();
TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
@@ -125,6 +129,7 @@ describe('EPersonFormComponent', () => {
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
+ { provide: AuthService, useValue: authService },
EPeopleRegistryComponent
],
schemas: [NO_ERRORS_SCHEMA]
@@ -228,4 +233,40 @@ describe('EPersonFormComponent', () => {
});
});
+ describe('impersonate', () => {
+ let ePersonId;
+
+ beforeEach(() => {
+ spyOn(authService, 'impersonate').and.callThrough();
+ ePersonId = 'testEPersonId';
+ component.epersonInitial = Object.assign(new EPerson(), {
+ id: ePersonId
+ });
+ component.impersonate();
+ });
+
+ it('should call authService.impersonate', () => {
+ expect(authService.impersonate).toHaveBeenCalledWith(ePersonId);
+ });
+
+ it('should set isImpersonated to true', () => {
+ expect(component.isImpersonated).toBe(true);
+ });
+ });
+
+ describe('stopImpersonating', () => {
+ beforeEach(() => {
+ spyOn(authService, 'stopImpersonatingAndRefresh').and.callThrough();
+ component.stopImpersonating();
+ });
+
+ it('should call authService.stopImpersonatingAndRefresh', () => {
+ expect(authService.stopImpersonatingAndRefresh).toHaveBeenCalled();
+ });
+
+ it('should set isImpersonated to false', () => {
+ expect(component.isImpersonated).toBe(false);
+ });
+ });
+
});
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts
index cbcaef78dc..9e3bcc88c0 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts
+++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts
@@ -7,7 +7,7 @@ import {
DynamicInputModel
} from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core';
-import { Subscription, combineLatest } from 'rxjs';
+import { Subscription, combineLatest, of } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { take } from 'rxjs/operators';
import { RestResponse } from '../../../../core/cache/response.models';
@@ -22,6 +22,7 @@ import { hasValue } from '../../../../shared/empty.util';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
+import { AuthService } from '../../../../core/auth/auth.service';
@Component({
selector: 'ds-eperson-form',
@@ -105,6 +106,24 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/
@Output() cancelForm: EventEmitter = new EventEmitter();
+ /**
+ * Observable whether or not the admin is allowed to reset the EPerson's password
+ * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
+ */
+ canReset$: Observable = of(false);
+
+ /**
+ * Observable whether or not the admin is allowed to delete the EPerson
+ * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false)
+ */
+ canDelete$: Observable = of(false);
+
+ /**
+ * Observable whether or not the admin is allowed to impersonate the EPerson
+ * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return true)
+ */
+ canImpersonate$: Observable = of(true);
+
/**
* List of subscriptions
*/
@@ -129,13 +148,22 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/
epersonInitial: EPerson;
+ /**
+ * Whether or not this EPerson is currently being impersonated
+ */
+ isImpersonated = false;
+
constructor(public epersonService: EPersonDataService,
public groupsDataService: GroupDataService,
private formBuilderService: FormBuilderService,
private translateService: TranslateService,
- private notificationsService: NotificationsService,) {
+ private notificationsService: NotificationsService,
+ private authService: AuthService) {
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
this.epersonInitial = eperson;
+ if (hasValue(eperson)) {
+ this.isImpersonated = this.authService.isImpersonatingUser(eperson.id);
+ }
}));
}
@@ -364,6 +392,22 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}));
}
+ /**
+ * Start impersonating the EPerson
+ */
+ impersonate() {
+ this.authService.impersonate(this.epersonInitial.id);
+ this.isImpersonated = true;
+ }
+
+ /**
+ * Stop impersonating the EPerson
+ */
+ stopImpersonating() {
+ this.authService.stopImpersonatingAndRefresh();
+ this.isImpersonated = false;
+ }
+
/**
* Cancel the current edit when component is destroyed & unsub all subscriptions
*/
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts
index f859c9f54e..5878500f1d 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts
+++ b/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts
@@ -23,15 +23,15 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service
import { PageInfo } from '../../../../core/shared/page-info.model';
import { UUIDService } from '../../../../core/shared/uuid.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { getMockFormBuilderService } from '../../../../shared/mocks/mock-form-builder-service';
-import { MockRouter } from '../../../../shared/mocks/mock-router';
-import { getMockTranslateService } from '../../../../shared/mocks/mock-translate.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
-import { MockTranslateLoader } from '../../../../shared/testing/mock-translate-loader';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { GroupFormComponent } from './group-form.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
+import { RouterMock } from '../../../../shared/mocks/router.mock';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
describe('GroupFormComponent', () => {
let component: GroupFormComponent;
@@ -90,13 +90,13 @@ describe('GroupFormComponent', () => {
};
builderService = getMockFormBuilderService();
translateService = getMockTranslateService();
- router = new MockRouter();
+ router = new RouterMock();
TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts
index 07fab49ca5..7cefda27dc 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts
+++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts
@@ -16,17 +16,17 @@ import { EPerson } from '../../../../../core/eperson/models/eperson.model';
import { Group } from '../../../../../core/eperson/models/group.model';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
-import { getMockFormBuilderService } from '../../../../../shared/mocks/mock-form-builder-service';
-import { MockRouter } from '../../../../../shared/mocks/mock-router';
-import { getMockTranslateService } from '../../../../../shared/mocks/mock-translate.service';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
-import { EPersonMock, EPersonMock2 } from '../../../../../shared/testing/eperson-mock';
import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
-import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
-import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service-stub';
import { of as observableOf } from 'rxjs';
-import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
import { MembersListComponent } from './members-list.component';
+import { EPersonMock, EPersonMock2 } from '../../../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
+import { getMockTranslateService } from '../../../../../shared/mocks/translate.service.mock';
+import { getMockFormBuilderService } from '../../../../../shared/mocks/form-builder-service.mock';
+import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub';
+import { RouterMock } from '../../../../../shared/mocks/router.mock';
describe('MembersListComponent', () => {
let component: MembersListComponent;
@@ -119,7 +119,7 @@ describe('MembersListComponent', () => {
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
@@ -129,7 +129,7 @@ describe('MembersListComponent', () => {
{ provide: GroupDataService, useValue: groupsDataServiceStub },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
{ provide: FormBuilderService, useValue: builderService },
- { provide: Router, useValue: new MockRouter() },
+ { provide: Router, useValue: new RouterMock() },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
index 4bd088b513..68942289ff 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
+++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
@@ -14,16 +14,16 @@ import { GroupDataService } from '../../../../../core/eperson/group-data.service
import { Group } from '../../../../../core/eperson/models/group.model';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
-import { getMockFormBuilderService } from '../../../../../shared/mocks/mock-form-builder-service';
-import { MockRouter } from '../../../../../shared/mocks/mock-router';
-import { getMockTranslateService } from '../../../../../shared/mocks/mock-translate.service';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
-import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
-import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service-stub';
import { of as observableOf } from 'rxjs';
-import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
import { SubgroupsListComponent } from './subgroups-list.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
+import { RouterMock } from '../../../../../shared/mocks/router.mock';
+import { getMockFormBuilderService } from '../../../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub';
describe('SubgroupsListComponent', () => {
let component: SubgroupsListComponent;
@@ -82,7 +82,7 @@ describe('SubgroupsListComponent', () => {
return observableOf(new RestResponse(true, 200, 'Success'));
}
};
- routerStub = new MockRouter();
+ routerStub = new RouterMock();
builderService = getMockFormBuilderService();
translateService = getMockTranslateService();
TestBed.configureTestingModule({
@@ -90,7 +90,7 @@ describe('SubgroupsListComponent', () => {
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts
index 7dff4f9bd1..deca1b951b 100644
--- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts
+++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts
@@ -15,15 +15,15 @@ import { EPerson } from '../../../core/eperson/models/eperson.model';
import { Group } from '../../../core/eperson/models/group.model';
import { RouteService } from '../../../core/services/route.service';
import { PageInfo } from '../../../core/shared/page-info.model';
-import { MockRouter } from '../../../shared/mocks/mock-router';
-import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson-mock';
import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
-import { routeServiceStub } from '../../../shared/testing/route-service-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
import { GroupsRegistryComponent } from './groups-registry.component';
+import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
+import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { routeServiceStub } from '../../../shared/testing/route-service.stub';
+import { RouterMock } from '../../../shared/mocks/router.mock';
describe('GroupRegistryComponent', () => {
let component: GroupsRegistryComponent;
@@ -82,7 +82,7 @@ describe('GroupRegistryComponent', () => {
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
- useClass: MockTranslateLoader
+ useClass: TranslateLoaderMock
}
}),
],
@@ -92,7 +92,7 @@ describe('GroupRegistryComponent', () => {
{ provide: GroupDataService, useValue: groupsDataServiceStub },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
{ provide: RouteService, useValue: routeServiceStub },
- { provide: Router, useValue: new MockRouter() },
+ { provide: Router, useValue: new RouterMock() },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts
index c8ab102d30..6f606b6801 100644
--- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts
+++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts
@@ -47,7 +47,7 @@ export class GroupsRegistryComponent implements OnInit {
// Current search in groups registry
currentSearchQuery: string;
- constructor(private groupService: GroupDataService,
+ constructor(public groupService: GroupDataService,
private ePersonDataService: EPersonDataService,
private translateService: TranslateService,
private notificationsService: NotificationsService,
diff --git a/src/app/+admin/admin-registries/admin-registries-routing.module.ts b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
index afdc46bf17..8833b307b9 100644
--- a/src/app/+admin/admin-registries/admin-registries-routing.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
@@ -4,6 +4,7 @@ import { NgModule } from '@angular/core';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { getRegistriesModulePath } from '../admin-routing.module';
+import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
const BITSTREAMFORMATS_MODULE_PATH = 'bitstream-formats';
@@ -14,16 +15,28 @@ export function getBitstreamFormatsModulePath() {
@NgModule({
imports: [
RouterModule.forChild([
- {path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}},
{
- path: 'metadata/:schemaName',
- component: MetadataSchemaComponent,
- data: {title: 'admin.registries.schema.title'}
+ path: 'metadata',
+ resolve: { breadcrumb: I18nBreadcrumbResolver },
+ data: {title: 'admin.registries.metadata.title', breadcrumbKey: 'admin.registries.metadata'},
+ children: [
+ {
+ path: '',
+ component: MetadataRegistryComponent
+ },
+ {
+ path: ':schemaName',
+ resolve: { breadcrumb: I18nBreadcrumbResolver },
+ component: MetadataSchemaComponent,
+ data: {title: 'admin.registries.schema.title', breadcrumbKey: 'admin.registries.schema'}
+ }
+ ]
},
{
path: BITSTREAMFORMATS_MODULE_PATH,
+ resolve: { breadcrumb: I18nBreadcrumbResolver },
loadChildren: './bitstream-formats/bitstream-formats.module#BitstreamFormatsModule',
- data: {title: 'admin.registries.bitstream-formats.title'}
+ data: {title: 'admin.registries.bitstream-formats.title', breadcrumbKey: 'admin.registries.bitstream-formats'}
},
])
]
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
index 4ad3553523..8b33463aa0 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
@@ -11,8 +11,8 @@ import { BitstreamFormatDataService } from '../../../../core/data/bitstream-form
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
-import { RouterStub } from '../../../../shared/testing/router-stub';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { RouterStub } from '../../../../shared/testing/router.stub';
import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
describe('AddBitstreamFormatComponent', () => {
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
index 67f6aa373e..2f08f8257c 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
@@ -4,6 +4,7 @@ import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
+import { I18nBreadcrumbResolver } from '../../../core/breadcrumbs/i18n-breadcrumb.resolver';
const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
const BITSTREAMFORMAT_ADD_PATH = 'add';
@@ -17,14 +18,18 @@ const BITSTREAMFORMAT_ADD_PATH = 'add';
},
{
path: BITSTREAMFORMAT_ADD_PATH,
+ resolve: { breadcrumb: I18nBreadcrumbResolver },
component: AddBitstreamFormatComponent,
+ data: {breadcrumbKey: 'admin.registries.bitstream-formats.create'}
},
{
path: BITSTREAMFORMAT_EDIT_PATH,
component: EditBitstreamFormatComponent,
resolve: {
- bitstreamFormat: BitstreamFormatsResolver
- }
+ bitstreamFormat: BitstreamFormatsResolver,
+ breadcrumb: I18nBreadcrumbResolver
+ },
+ data: {breadcrumbKey: 'admin.registries.bitstream-formats.edit'}
},
])
],
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
index 7ff15cad6d..2acfa17c8b 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
@@ -11,10 +11,10 @@ import { PaginationComponent } from '../../../shared/pagination/pagination.compo
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { HostWindowService } from '../../../shared/host-window.service';
-import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
index 87e44a6fec..08770dddf2 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
@@ -12,8 +12,8 @@ import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
-import { RouterStub } from '../../../../shared/testing/router-stub';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { RouterStub } from '../../../../shared/testing/router.stub';
import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
describe('EditBitstreamFormatComponent', () => {
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
index efc8854587..fc1b4d50a0 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
-import { RouterStub } from '../../../../shared/testing/router-stub';
+import { RouterStub } from '../../../../shared/testing/router.stub';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { FormatFormComponent } from './format-form.component';
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html
index a254f20428..42b7558397 100644
--- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html
@@ -11,7 +11,6 @@
0"
[paginationOptions]="config"
- [pageInfoState]="(metadataSchemas | async)?.payload"
[collectionSize]="(metadataSchemas | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true"
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts
index 409e6f988e..bbfd256036 100644
--- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts
@@ -10,14 +10,14 @@ import { RegistryService } from '../../../core/registry/registry.service';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
-import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
import { HostWindowService } from '../../../shared/host-window.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { RestResponse } from '../../../core/cache/response.models';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
describe('MetadataRegistryComponent', () => {
let comp: MetadataRegistryComponent;
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts
index 302974b5c2..cd845329c6 100644
--- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts
@@ -4,7 +4,7 @@ import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
-import { map, take } from 'rxjs/operators';
+import { filter, map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip';
@@ -12,6 +12,8 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { Route, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
+import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
+import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
@Component({
selector: 'ds-metadata-registry',
@@ -37,6 +39,11 @@ export class MetadataRegistryComponent {
pageSize: 25
});
+ /**
+ * Whether or not the list of MetadataSchemas needs an update
+ */
+ needsUpdate$: BehaviorSubject = new BehaviorSubject(true);
+
constructor(private registryService: RegistryService,
private notificationsService: NotificationsService,
private router: Router,
@@ -50,14 +57,17 @@ export class MetadataRegistryComponent {
*/
onPageChange(event) {
this.config.currentPage = event;
- this.updateSchemas();
+ this.forceUpdateSchemas();
}
/**
* Update the list of schemas by fetching it from the rest api or cache
*/
private updateSchemas() {
- this.metadataSchemas = this.registryService.getMetadataSchemas(this.config);
+ this.metadataSchemas = this.needsUpdate$.pipe(
+ filter((update) => update === true),
+ switchMap(() => this.registryService.getMetadataSchemas(toFindListOptions(this.config)))
+ );
}
/**
@@ -65,8 +75,7 @@ export class MetadataRegistryComponent {
* a new REST call
*/
public forceUpdateSchemas() {
- this.registryService.clearMetadataSchemaRequests().subscribe();
- this.updateSchemas();
+ this.needsUpdate$.next(true);
}
/**
@@ -125,6 +134,7 @@ export class MetadataRegistryComponent {
* Delete all the selected metadata schemas
*/
deleteSchemas() {
+ this.registryService.clearMetadataSchemaRequests().subscribe();
this.registryService.getSelectedMetadataSchemas().pipe(take(1)).subscribe(
(schemas) => {
const tasks$ = [];
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts
index db2294ab59..a840d68dcf 100644
--- a/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts
@@ -21,7 +21,8 @@ describe('MetadataSchemaFormComponent', () => {
const registryServiceStub = {
getActiveMetadataSchema: () => observableOf(undefined),
createOrUpdateMetadataSchema: (schema: MetadataSchema) => observableOf(schema),
- cancelEditMetadataSchema: () => {}
+ cancelEditMetadataSchema: () => {},
+ clearMetadataSchemaRequests: () => observableOf(undefined)
};
const formBuilderServiceStub = {
createFormGroup: () => {
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
index 23e7309a00..79129d68a4 100644
--- a/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
+++ b/src/app/+admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
@@ -128,6 +128,7 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
* Emit the updated/created schema using the EventEmitter submitForm
*/
onSubmit() {
+ this.registryService.clearMetadataSchemaRequests().subscribe();
this.registryService.getActiveMetadataSchema().pipe(take(1)).subscribe(
(schema) => {
const values = {
@@ -139,7 +140,7 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
this.submitForm.emit(newSchema);
});
} else {
- this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), {
+ this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, {
id: schema.id,
prefix: (values.prefix ? values.prefix : schema.prefix),
namespace: (values.namespace ? values.namespace : schema.namespace)
@@ -148,6 +149,7 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
});
}
this.clearFields();
+ this.registryService.cancelEditMetadataSchema();
}
);
}
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts
index 402f9c0c86..98128a6a61 100644
--- a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts
@@ -30,6 +30,7 @@ describe('MetadataFieldFormComponent', () => {
createOrUpdateMetadataField: (field: MetadataField) => observableOf(field),
cancelEditMetadataField: () => {},
cancelEditMetadataSchema: () => {},
+ clearMetadataFieldRequests: () => observableOf(undefined)
};
const formBuilderServiceStub = {
createFormGroup: () => {
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
index 0811530343..42f6441791 100644
--- a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
@@ -153,6 +153,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
* Emit the updated/created field using the EventEmitter submitForm
*/
onSubmit() {
+ this.registryService.clearMetadataFieldRequests().subscribe();
this.registryService.getActiveMetadataField().pipe(take(1)).subscribe(
(field) => {
const values = {
@@ -166,7 +167,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
this.submitForm.emit(newField);
});
} else {
- this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), {
+ this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), field, {
id: field.id,
schema: this.metadataSchema,
element: (values.element ? values.element : field.element),
@@ -177,6 +178,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
});
}
this.clearFields();
+ this.registryService.cancelEditMetadataField();
}
);
}
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html
index 4a7a4cf34d..49ef748349 100644
--- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html
@@ -1,36 +1,37 @@
;
@@ -159,7 +158,6 @@ describe('ItemBitstreamsComponent', () => {
{ provide: Router, useValue: router },
{ provide: ActivatedRoute, useValue: route },
{ provide: NotificationsService, useValue: notificationsService },
- { provide: GLOBAL_CONFIG, useValue: { item: { edit: { undoTimeout: 10 } } } as any },
{ provide: BitstreamDataService, useValue: bitstreamService },
{ provide: ObjectCacheService, useValue: objectCache },
{ provide: RequestService, useValue: requestService },
@@ -191,8 +189,21 @@ describe('ItemBitstreamsComponent', () => {
it('should not call deleteAndReturnResponse on the bitstreamService for the unmarked field', () => {
expect(bitstreamService.deleteAndReturnResponse).not.toHaveBeenCalledWith(bitstream1.id);
});
+ });
- it('should send out a patch for the move operations', () => {
+ describe('when dropBitstream is called', () => {
+ beforeEach((done) => {
+ comp.dropBitstream(bundle, {
+ fromIndex: 0,
+ toIndex: 50,
+ // tslint:disable-next-line:no-empty
+ finish: () => {
+ done();
+ }
+ })
+ });
+
+ it('should send out a patch for the move operation', () => {
expect(bundleService.patch).toHaveBeenCalled();
});
});
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
index bdb1ec23a5..45b8e23108 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
@@ -1,6 +1,6 @@
-import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core';
+import { ChangeDetectorRef, Component, NgZone, OnDestroy } from '@angular/core';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
-import { filter, map, switchMap, take, tap } from 'rxjs/operators';
+import { filter, map, switchMap, take } from 'rxjs/operators';
import { Observable } from 'rxjs/internal/Observable';
import { Subscription } from 'rxjs/internal/Subscription';
import { ItemDataService } from '../../../core/data/item-data.service';
@@ -8,10 +8,9 @@ import { ObjectUpdatesService } from '../../../core/data/object-updates/object-u
import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
-import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
-import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
-import { zip as observableZip, combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
+import { hasValue, isNotEmpty } from '../../../shared/empty.util';
+import { zip as observableZip, of as observableOf } from 'rxjs';
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { RequestService } from '../../../core/data/request.service';
@@ -23,8 +22,6 @@ import { Bundle } from '../../../core/shared/bundle.model';
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
-import { Operation } from 'fast-json-patch';
-import { MoveOperation } from 'fast-json-patch/lib/core';
import { BundleDataService } from '../../../core/data/bundle-data.service';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes';
@@ -86,15 +83,15 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
public router: Router,
public notificationsService: NotificationsService,
public translateService: TranslateService,
- @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
public route: ActivatedRoute,
public bitstreamService: BitstreamDataService,
public objectCache: ObjectCacheService,
public requestService: RequestService,
public cdRef: ChangeDetectorRef,
- public bundleService: BundleDataService
+ public bundleService: BundleDataService,
+ public zone: NgZone
) {
- super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route);
+ super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
}
/**
@@ -145,7 +142,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
/**
* Submit the current changes
- * Bitstreams that were dragged around send out a patch request with move operations to the rest API
* Bitstreams marked as deleted send out a delete request to the rest API
* Display notifications and reset the current item/updates
*/
@@ -153,32 +149,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
this.submitting = true;
const bundlesOnce$ = this.bundles$.pipe(take(1));
- // Fetch all move operations for each bundle
- const moveOperations$ = bundlesOnce$.pipe(
- switchMap((bundles: Bundle[]) => observableZip(...bundles.map((bundle: Bundle) =>
- this.objectUpdatesService.getMoveOperations(bundle.self).pipe(
- take(1),
- map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, {
- from: `/_links/bitstreams${operation.from}/href`,
- path: `/_links/bitstreams${operation.path}/href`
- }))])
- )
- )))
- );
-
- // Send out an immediate patch request for each bundle
- const patchResponses$ = observableCombineLatest(bundlesOnce$, moveOperations$).pipe(
- switchMap(([bundles, moveOperationList]: [Bundle[], Operation[][]]) =>
- observableZip(...bundles.map((bundle: Bundle, index: number) => {
- if (isNotEmpty(moveOperationList[index])) {
- return this.bundleService.patch(bundle, moveOperationList[index]);
- } else {
- return observableOf(undefined);
- }
- }))
- )
- );
-
// Fetch all removed bitstreams from the object update service
const removedBitstreams$ = bundlesOnce$.pipe(
switchMap((bundles: Bundle[]) => observableZip(
@@ -203,19 +173,42 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
);
// Perform the setup actions from above in order and display notifications
- patchResponses$.pipe(
- switchMap((responses: RestResponse[]) => {
- this.displayNotifications('item.edit.bitstreams.notifications.move', responses);
- return removedResponses$
- }),
- take(1)
- ).subscribe((responses: RestResponse[]) => {
+ removedResponses$.pipe(take(1)).subscribe((responses: RestResponse[]) => {
this.displayNotifications('item.edit.bitstreams.notifications.remove', responses);
this.reset();
this.submitting = false;
});
}
+ /**
+ * A bitstream was dropped in a new location. Send out a Move Patch request to the REST API, display notifications,
+ * refresh the bundle's cache (so the lists can properly reload) and call the event's callback function (which will
+ * navigate the user to the correct page)
+ * @param bundle The bundle to send patch requests to
+ * @param event The event containing the index the bitstream came from and was dropped to
+ */
+ dropBitstream(bundle: Bundle, event: any) {
+ this.zone.runOutsideAngular(() => {
+ if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) {
+ const moveOperation = Object.assign({
+ op: 'move',
+ from: `/_links/bitstreams/${event.fromIndex}/href`,
+ path: `/_links/bitstreams/${event.toIndex}/href`
+ });
+ this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => {
+ this.zone.run(() => {
+ this.displayNotifications('item.edit.bitstreams.notifications.move', [response]);
+ // Remove all cached requests from this bundle and call the event's callback when the requests are cleared
+ this.requestService.removeByHrefSubstring(bundle.self).pipe(
+ filter((isCached) => isCached),
+ take(1)
+ ).subscribe(() => event.finish());
+ });
+ });
+ }
+ });
+ }
+
/**
* Display notifications
* - Error notification for each failed response with their message
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
index 58273bb931..c28ef9b525 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html
@@ -17,5 +17,5 @@
-
+
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
index 115e326241..72e2055bf7 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core';
import { Bundle } from '../../../../core/shared/bundle.model';
import { Item } from '../../../../core/shared/item.model';
import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes';
@@ -36,6 +36,13 @@ export class ItemEditBitstreamBundleComponent implements OnInit {
*/
@Input() columnSizes: ResponsiveTableSizes;
+ /**
+ * Send an event when the user drops an object on the pagination
+ * The event contains details about the index the object came from and is dropped to (across the entirety of the list,
+ * not just within a single page)
+ */
+ @Output() dropObject: EventEmitter = new EventEmitter();
+
/**
* The bootstrap sizes used for the Bundle Name column
* This column stretches over the first 3 columns and thus is a combination of their sizes processed in ngOnInit
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html
index 25941f472e..9197b89796 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html
@@ -7,24 +7,29 @@
[collectionSize]="(objectsRD$ | async)?.payload?.totalElements"
[disableRouteParameterUpdate]="true"
(pageChange)="switchPage($event)">
-