Merge remote-tracking branch 'origin/main' into DSC-287-Show-an-error-page-if-the-REST-API-is-not-available

# Conflicts:
#	src/app/app-routing.module.ts
This commit is contained in:
Giuseppe Digilio
2021-12-09 15:23:16 +01:00
32 changed files with 2253 additions and 834 deletions

View File

@@ -29,11 +29,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v1
uses: actions/checkout@v2
# https://github.com/actions/setup-node
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
@@ -82,7 +82,7 @@ jobs:
# Upload coverage reports to Codecov (for Node v12 only)
# https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v2
if: matrix.node-version == '12.x'
# Using docker-compose start backend using CI configuration

78
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# DSpace Docker image build for hub.docker.com
name: Docker images
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
on:
push:
branches:
- main
- 'dspace-**'
tags:
- 'dspace-**'
pull_request:
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
if: github.repository == 'dspace/dspace-angular'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v2
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v1
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'dspace/dspace-angular' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
id: meta_build
uses: docker/metadata-action@v3
with:
images: dspace/dspace-angular
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'dspace-angular' image
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}

View File

@@ -3,5 +3,6 @@
"i18n-ally.localesPaths": [
"src/assets/i18n",
"src/app/core/locale"
]
],
"typescript.tsdk": "node_modules\\typescript\\lib"
}

View File

@@ -1,7 +1,7 @@
# This image will be published as dspace/dspace-angular
# See https://dspace-labs.github.io/DSpace-Docker-Images/ for usage details
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
FROM node:12-alpine
FROM node:14-alpine
WORKDIR /app
ADD . /app/
EXPOSE 4000

18
LICENSE
View File

@@ -1,4 +1,4 @@
DSpace source code BSD License:
BSD 3-Clause License
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
@@ -13,13 +13,12 @@ notice, this list of conditions and the following disclaimer.
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name DuraSpace nor the name of the DSpace Foundation
nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
- Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
@@ -30,10 +29,3 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
DSpace uses third-party libraries which may be distributed under
different licenses to the above. Information about these licenses
is detailed in the LICENSES_THIRD_PARTY file at the root of the source
tree. You must agree to the terms of these licenses, in addition to
the above DSpace source code license, in order to use this software.

28
NOTICE Normal file
View File

@@ -0,0 +1,28 @@
Licenses of Third-Party Libraries
=================================
DSpace uses third-party libraries which may be distributed under
different licenses than specified in our LICENSE file. Information
about these licenses is detailed in the LICENSES_THIRD_PARTY file at
the root of the source tree. You must agree to the terms of these
licenses, in addition to the DSpace source code license, in order to
use this software.
Licensing Notices
=================
[July 2019] DuraSpace joined with LYRASIS (another 501(c)3 organization) in July 2019.
LYRASIS holds the copyrights of DuraSpace.
[July 2009] Fedora Commons joined with the DSpace Foundation and began operating under
the new name DuraSpace in July 2009. DuraSpace holds the copyrights of
the DSpace Foundation, Inc.
[July 2007] The DSpace Foundation, Inc. is a 501(c)3 corporation established in July 2007
with a mission to promote and advance the dspace platform enabling management,
access and preservation of digital works. The Foundation was able to transfer
the legal copyright from Hewlett-Packard Company (HP) and Massachusetts
Institute of Technology (MIT) to the DSpace Foundation in October 2007. Many
of the files in the source code may contain a copyright statement stating HP
and MIT possess the copyright, in these instances please note that the copy
right has transferred to the DSpace foundation, and subsequently to DuraSpace.

View File

@@ -449,4 +449,8 @@ DSpace uses GitHub to track issues:
License
-------
This project's source code is made available under the DSpace BSD License: http://www.dspace.org/license
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/
DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed
in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file.

View File

@@ -4,6 +4,20 @@
:warning: **NOT PRODUCTION READY** The below Docker Compose resources are not guaranteed "production ready" at this time. They have been built for development/testing only. Therefore, DSpace Docker images may not be fully secured or up-to-date. While you are welcome to base your own images on these DSpace images/resources, these should not be used "as is" in any production scenario.
***
## 'Dockerfile' in root directory
This Dockerfile is used to build a *development* DSpace 7 Angular UI image, published as 'dspace/dspace-angular'
```
docker build -t dspace/dspace-angular:dspace-7_x .
```
This image is built *automatically* after each commit is made to the `main` branch.
Admins to our DockerHub repo can manually publish with the following command.
```
docker push dspace/dspace-angular:dspace-7_x
```
## docker directory
- docker-compose.yml
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.

View File

@@ -20,7 +20,7 @@ services:
DSPACE_NAMESPACE: /
DSPACE_PORT: '4000'
DSPACE_SSL: "false"
image: dspace/dspace-angular:latest
image: dspace/dspace-angular:dspace-7_x
build:
context: ..
dockerfile: Dockerfile

View File

@@ -63,26 +63,26 @@
"webdriver-manager": "^12.1.8"
},
"dependencies": {
"@angular/animations": "~10.2.3",
"@angular/cdk": "^10.2.6",
"@angular/common": "~10.2.3",
"@angular/compiler": "~10.2.3",
"@angular/core": "~10.2.3",
"@angular/forms": "~10.2.3",
"@angular/localize": "10.2.3",
"@angular/platform-browser": "~10.2.3",
"@angular/platform-browser-dynamic": "~10.2.3",
"@angular/platform-server": "~10.2.3",
"@angular/router": "~10.2.3",
"@angular/animations": "~11.2.14",
"@angular/cdk": "^11.2.13",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/forms": "~11.2.14",
"@angular/localize": "11.2.14",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"@angular/platform-server": "~11.2.14",
"@angular/router": "~11.2.14",
"@angularclass/bootloader": "1.0.1",
"@kolkov/ngx-gallery": "^1.2.3",
"@ng-bootstrap/ng-bootstrap": "7.0.0",
"@ng-dynamic-forms/core": "^12.0.0",
"@ng-dynamic-forms/ui-ng-bootstrap": "^12.0.0",
"@ngrx/effects": "^10.0.1",
"@ngrx/router-store": "^10.0.1",
"@ngrx/store": "^10.0.1",
"@nguniversal/express-engine": "10.1.0",
"@ng-bootstrap/ng-bootstrap": "9.1.3",
"@ng-dynamic-forms/core": "^13.0.0",
"@ng-dynamic-forms/ui-ng-bootstrap": "^13.0.0",
"@ngrx/effects": "^11.1.1",
"@ngrx/router-store": "^11.1.1",
"@ngrx/store": "^11.1.1",
"@nguniversal/express-engine": "11.2.1",
"@ngx-translate/core": "^13.0.0",
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
"angular-idle-preload": "3.0.0",
@@ -115,13 +115,13 @@
"mirador-share-plugin": "^0.10.0",
"moment": "^2.29.1",
"morgan": "^1.10.0",
"ng-mocks": "10.5.4",
"ng-mocks": "11.11.2",
"ng2-file-upload": "1.4.0",
"ng2-nouislider": "^1.8.3",
"ngx-infinite-scroll": "^10.0.1",
"ngx-moment": "^5.0.0",
"ngx-pagination": "5.0.0",
"ngx-sortablejs": "^10.0.0",
"ngx-sortablejs": "^11.1.0",
"nouislider": "^14.6.3",
"pem": "1.14.4",
"postcss-cli": "^8.3.0",
@@ -138,25 +138,25 @@
},
"devDependencies": {
"@angular-builders/custom-webpack": "10.0.1",
"@angular-devkit/build-angular": "~0.1002.3",
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.3",
"@angular/language-service": "~10.2.3",
"@angular-devkit/build-angular": "~0.1102.15",
"@angular/cli": "~11.2.15",
"@angular/compiler-cli": "~11.2.14",
"@angular/language-service": "~11.2.14",
"@cypress/schematic": "^1.5.0",
"@fortawesome/fontawesome-free": "^5.5.0",
"@ngrx/store-devtools": "^10.0.1",
"@ngrx/store-devtools": "^11.1.1",
"@ngtools/webpack": "10.2.3",
"@nguniversal/builders": "~10.1.0",
"@nguniversal/builders": "~11.2.1",
"@types/deep-freeze": "0.1.2",
"@types/express": "^4.17.9",
"@types/file-saver": "^2.0.1",
"@types/jasmine": "^3.6.2",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.8",
"@types/js-cookie": "2.2.6",
"@types/lodash": "^4.14.165",
"@types/node": "^14.14.9",
"axe-core": "^4.3.3",
"codelyzer": "^6.0.1",
"codelyzer": "^6.0.0",
"compression-webpack-plugin": "^3.0.1",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "3.4.0",
@@ -169,14 +169,14 @@
"fork-ts-checker-webpack-plugin": "^6.0.3",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.0",
"jasmine-core": "^3.6.0",
"jasmine-core": "~3.6.0",
"jasmine-marbles": "0.6.0",
"jasmine-spec-reporter": "^6.0.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "^5.2.3",
"karma-chrome-launcher": "^3.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "^4.0.1",
"karma-jasmine-html-reporter": "^1.5.4",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-mocha-reporter": "2.2.5",
"nodemon": "^2.0.2",
"npm-run-all": "^4.1.5",

View File

@@ -216,7 +216,8 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard';
}
], {
onSameUrlNavigation: 'reload',
})
relativeLinkResolution: 'legacy'
})
],
exports: [RouterModule],
})

View File

@@ -5,9 +5,10 @@
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionName$ |async) }" id="collection-name"></p>
<p>{{'collection.edit.item-mapper.description' | translate}}</p>
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
<ngb-tab title="{{'collection.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
<ng-template ngbTabContent>
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
<li [ngbNavItem]="'browseTab'">
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.browse' | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-2">
<ds-item-select class="mt-2"
[key]="'browse'"
@@ -21,9 +22,10 @@
(cancel)="onCancel()"></ds-item-select>
</div>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'collection.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
<ng-template ngbTabContent>
</li>
<li [ngbNavItem]="'mapTab'">
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.map' | translate}}</a>
<ng-template ngbNavContent>
<div class="row mt-2">
<div class="col-12 col-lg-6">
<ds-search-form id="search-form"
@@ -52,8 +54,9 @@
{{'collection.edit.item-mapper.no-search' | translate}}
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</div>
</div>
</div>

View File

@@ -5,9 +5,10 @@
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemName$ | async) }" id="item-name"></p>
<p>{{'item.edit.item-mapper.description' | translate}}</p>
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
<ngb-tab title="{{'item.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
<ng-template ngbTabContent>
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
<li [ngbNavItem]="'browseTab'">
<a ngbNavLink>{{'item.edit.item-mapper.tabs.browse' | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-2">
<ds-collection-select class="mt-2"
[key]="'browse'"
@@ -20,9 +21,10 @@
(cancel)="onCancel()"></ds-collection-select>
</div>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'item.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
<ng-template ngbTabContent>
</li>
<li [ngbNavItem]="'mapTab'">
<a ngbNavLink>{{'item.edit.item-mapper.tabs.map' | translate}}</a>
<ng-template ngbNavContent>
<div class="row mt-2">
<div class="col-12 col-lg-6">
<ds-search-form id="search-form"
@@ -50,8 +52,9 @@
{{'item.edit.item-mapper.no-search' | translate}}
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
@@ -55,7 +55,7 @@ describe('MediaViewerImageComponent', () => {
]
);
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports:[],
declarations: [MediaViewerImageComponent],

View File

@@ -1,5 +1,5 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
@@ -17,7 +17,7 @@ describe('MediaViewerVideoComponent', () => {
let component: MediaViewerVideoComponent;
let fixture: ComponentFixture<MediaViewerVideoComponent>;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({

View File

@@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Bitstream } from '../../core/shared/bitstream.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
@@ -60,7 +60,7 @@ describe('MediaViewerComponent', () => {
{ bitstream: mockBitstream, format: 'image', thumbnail: null }
);
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({

View File

@@ -1,17 +1,21 @@
<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
<ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
<ng-template ngbTabContent>
<div class="mt-4">
<ds-related-entities-search [item]="item"
[relationType]="relationType.filter"
[configuration]="relationType.configuration"
[searchEnabled]="searchEnabled"
[sideBarWidth]="sideBarWidth">
</ds-related-entities-search>
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
<ng-container *ngIf="relationTypes.length > 1">
<ul ngbNav #tabs="ngbNav" [destroyOnHide]="true" [activeId]="activeTab$ | async" (navChange)="onTabChange($event)" class="nav-tabs">
<li *ngFor="let relationType of relationTypes" [ngbNavItem]="relationType.filter">
<a ngbNavLink>{{'item.page.relationships.' + relationType.label | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-4">
<ds-related-entities-search [item]="item"
[relationType]="relationType.filter"
[configuration]="relationType.configuration"
[searchEnabled]="searchEnabled"
[sideBarWidth]="sideBarWidth">
</ds-related-entities-search>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</ng-container>
<div *ngIf="relationTypes.length === 1" class="mt-4">
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
[relationType]="relationType.filter"

View File

@@ -1,5 +1,5 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@@ -75,7 +75,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
};
describe('With only one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
@@ -126,7 +126,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
});
describe('With more than one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,

View File

@@ -1,5 +1,5 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@@ -79,7 +79,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
};
describe('With only one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
@@ -130,7 +130,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
});
describe('With more than one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,

View File

@@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthService } from '../../core/auth/auth.service';
import { FileService } from '../../core/shared/file.service';
import { of as observableOf } from 'rxjs';
@@ -77,7 +77,7 @@ describe('BitstreamDownloadPageComponent', () => {
}
describe('init', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
initTestbed();
}));
@@ -93,7 +93,7 @@ describe('BitstreamDownloadPageComponent', () => {
describe('bitstream retrieval', () => {
describe('when the user is authorized and not logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
@@ -109,7 +109,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is authorized and logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
initTestbed();
}));
@@ -123,7 +123,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is not authorized and logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
initTestbed();
@@ -138,7 +138,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is not authorized and not logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));

View File

@@ -25,7 +25,7 @@
</div>
<ul class="list-unstyled">
<li *ngFor="let object of objects?.payload?.page" class="mt-4 mb-4">
<ds-listable-object-component-loader [object]="object"></ds-listable-object-component-loader>
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode"></ds-listable-object-component-loader>
</li>
</ul>
<div>

View File

@@ -2,7 +2,7 @@ import { BrowseByComponent } from './browse-by.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { SharedModule } from '../shared.module';
import { CommonModule } from '@angular/common';
@@ -18,17 +18,31 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
import { storeModuleConfig } from '../../app.reducer';
import { FindListOptions } from '../../core/data/request.models';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../testing/pagination-service.stub';
import { ListableObjectComponentLoaderComponent } from '../object-collection/shared/listable-object/listable-object-component-loader.component';
import { ViewMode } from '../../core/shared/view-mode.model';
import { BrowseEntryListElementComponent } from '../object-list/browse-entry-list-element/browse-entry-list-element.component';
import {
DEFAULT_CONTEXT,
listableObjectComponent,
} from '../object-collection/shared/listable-object/listable-object.decorator';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
import { ITEM } from '../../core/shared/item.resource-type';
import { ThemeService } from '../theme-support/theme.service';
import SpyObj = jasmine.SpyObj;
@listableObjectComponent(BrowseEntry, ViewMode.ListElement, DEFAULT_CONTEXT, 'custom')
@Component({
selector: 'ds-browse-entry-list-element',
template: ''
})
class MockThemedBrowseEntryListElementComponent {}
describe('BrowseByComponent', () => {
let comp: BrowseByComponent;
let fixture: ComponentFixture<BrowseByComponent>;
let themeService: ThemeService;
const mockItems = [
Object.assign(new Item(), {
id: 'fakeId-1',
@@ -59,9 +73,12 @@ describe('BrowseByComponent', () => {
});
const paginationService = new PaginationServiceStub(paginationConfig);
let themeService: SpyObj<ThemeService>;
beforeEach(waitForAsync(() => {
themeService = jasmine.createSpyObj('themeService', {
getThemeName: 'dspace',
getThemeName$: observableOf('dspace'),
});
TestBed.configureTestingModule({
imports: [
@@ -82,6 +99,7 @@ describe('BrowseByComponent', () => {
declarations: [],
providers: [
{provide: PaginationService, useValue: paginationService},
{provide: MockThemedBrowseEntryListElementComponent},
{ provide: ThemeService, useValue: themeService },
],
schemas: [NO_ERRORS_SCHEMA]
@@ -170,4 +188,67 @@ describe('BrowseByComponent', () => {
});
});
describe('when enableArrows is true and browseEntries are provided', () => {
let browseEntries;
beforeEach(() => {
browseEntries = [
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: 'authority key 1',
value: 'browse entry 1',
language: null,
count: 1,
}),
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: null,
value: 'browse entry 2',
language: null,
count: 4,
}),
];
comp.enableArrows = true;
comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), browseEntries));
comp.paginationConfig = paginationConfig;
comp.sortConfig = Object.assign(new SortOptions('dc.title', SortDirection.ASC));
// NOTE: do NOT trigger change detection until the theme is set, such that the theme can be picked up as well
});
describe('when theme is base', () => {
beforeEach(() => {
themeService.getThemeName.and.returnValue('base');
themeService.getThemeName$.and.returnValue(observableOf('base'));
fixture.detectChanges();
});
it('should use the base component to render browse entries', () => {
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
expect(componentLoaders.length).toEqual(browseEntries.length);
componentLoaders.forEach((componentLoader) => {
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
expect(browseEntry.componentInstance).toBeInstanceOf(BrowseEntryListElementComponent);
});
});
});
describe('when theme is custom', () => {
beforeEach(() => {
themeService.getThemeName.and.returnValue('custom');
themeService.getThemeName$.and.returnValue(observableOf('custom'));
fixture.detectChanges();
});
it('should use the themed component to render browse entries', () => {
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
expect(componentLoaders.length).toEqual(browseEntries.length);
componentLoaders.forEach((componentLoader) => {
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
expect(browseEntry.componentInstance).toBeInstanceOf(MockThemedBrowseEntryListElementComponent);
});
});
});
});
});

View File

@@ -8,6 +8,7 @@ import { Observable } from 'rxjs';
import { ListableObject } from '../object-collection/shared/listable-object.model';
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
import { PaginationService } from '../../core/pagination/pagination.service';
import { ViewMode } from '../../core/shared/view-mode.model';
@Component({
selector: 'ds-browse-by',
@@ -22,6 +23,12 @@ import { PaginationService } from '../../core/pagination/pagination.service';
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent implements OnInit {
/**
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
*/
viewMode: ViewMode = ViewMode.ListElement;
/**
* The i18n message to display as title
*/

View File

@@ -3,7 +3,7 @@
class="form-control"
(click)="$event.stopPropagation();"
placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}"
[formControl]="input" dsAutoFocus (keyup.enter)="selectSingleResult()">
[formControl]="input" ngbAutofocus (keyup.enter)="selectSingleResult()">
</div>
<div class="dropdown-divider"></div>
<div class="scrollable-menu list-group">

View File

@@ -7,59 +7,64 @@
</div>
<div class="modal-body">
<ds-loading *ngIf="!item || !collection"></ds-loading>
<ngb-tabset *ngIf="item && collection">
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : {count: (totalInternal$ | async)}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-search-tab
[selection$]="selection$"
[listId]="listId"
[relationship]="relationshipOptions"
[repeatable]="repeatable"
[context]="context"
[query]="query"
[relationshipType]="relationshipType"
[isLeft]="isLeft"
[item]="item"
[isEditRelationship]="isEditRelationship"
[toRemove]="toRemove"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-search-tab>
</ng-template>
</ngb-tab>
<ngb-tab *ngFor="let source of (externalSourcesRD$ | async); let idx = index"
[title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : {count: (totalExternal$ | async)[idx]}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-external-source-tab
[label]="label"
[listId]="listId"
[repeatable]="repeatable"
[item]="item"
[collection]="collection"
[relationship]="relationshipOptions"
[context]="context"
[externalSource]="source"
(importedObject)="imported($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-external-source-tab>
</ng-template>
</ngb-tab>
<ngb-tab *ngIf="!isEditRelationship" [title]="'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : {count: (selection$ | async)?.length}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-selection-tab
[selection$]="selection$"
[listId]="listId"
[relationshipType]="relationshipOptions.relationshipType"
[repeatable]="repeatable"
[context]="context"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-selection-tab>
</ng-template>
</ngb-tab>
</ngb-tabset>
<ng-container *ngIf="item && collection">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : { count: (totalInternal$ | async)} }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-search-tab
[selection$]="selection$"
[listId]="listId"
[relationship]="relationshipOptions"
[repeatable]="repeatable"
[context]="context"
[query]="query"
[relationshipType]="relationshipType"
[isLeft]="isLeft"
[item]="item"
[isEditRelationship]="isEditRelationship"
[toRemove]="toRemove"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-search-tab>
</ng-template>
</li>
<li ngbNavItem *ngFor="let source of (externalSourcesRD$ | async); let idx = index">
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : { count: (totalExternal$ | async)[idx] } }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-external-source-tab
[label]="label"
[listId]="listId"
[repeatable]="repeatable"
[item]="item"
[collection]="collection"
[relationship]="relationshipOptions"
[context]="context"
[externalSource]="source"
(importedObject)="imported($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-external-source-tab>
</ng-template>
</li>
<li ngbNavItem *ngIf="!isEditRelationship">
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : { count: (selection$ | async)?.length } }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-selection-tab
[selection$]="selection$"
[listId]="listId"
[relationshipType]="relationshipOptions.relationshipType"
[repeatable]="repeatable"
[context]="context"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-selection-tab>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav"></div>
</ng-container>
</div>
<div class="modal-footer">
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate: {size: (selection$ | async)?.length || 0}) }}</small>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
@@ -60,7 +60,7 @@ mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowit
const linkService = getMockLinkService();
describe('ClaimedApprovedSearchResultListElementComponent', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective],
@@ -75,7 +75,7 @@ describe('ClaimedApprovedSearchResultListElementComponent', () => {
}).compileComponents();
}));
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ClaimedApprovedSearchResultListElementComponent);
component = fixture.componentInstance;
}));

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
@@ -60,7 +60,7 @@ mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowit
const linkService = getMockLinkService();
describe('ClaimedDeclinedSearchResultListElementComponent', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective],
@@ -75,7 +75,7 @@ describe('ClaimedDeclinedSearchResultListElementComponent', () => {
}).compileComponents();
}));
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ClaimedDeclinedSearchResultListElementComponent);
component = fixture.componentInstance;
}));

View File

@@ -8,19 +8,24 @@
<div class="container-fluid">
<label for="ResourcePolicyObject">{{'resource-policies.form.eperson-group-list.label' | translate}}</label>
<input id="ResourcePolicyObject" class="form-control mb-3" type="text" readonly [value]="resourcePolicyTargetName$ | async">
<ngb-tabset *ngIf="canSetGrant()" type="pills">
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.eperson' | translate">
<ng-template ngbTabContent>
<ds-eperson-group-list (select)="updateObjectSelected($event, true)"></ds-eperson-group-list>
</ng-template>
</ngb-tab>
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.group' | translate">
<ng-template ngbTabContent>
<ds-eperson-group-list [isListOfEPerson]="false"
(select)="updateObjectSelected($event, false)"></ds-eperson-group-list>
</ng-template>
</ngb-tab>
</ngb-tabset>
<ng-container *ngIf="canSetGrant()">
<ul ngbNav #nav="ngbNav" class="nav-pills">
<li ngbNavItem>
<a ngbNavLink>{{'resource-policies.form.eperson-group-list.tab.eperson' | translate}}</a>
<ng-template ngbNavContent>
<ds-eperson-group-list (select)="updateObjectSelected($event, true)"></ds-eperson-group-list>
</ng-template>
</li>
<li ngbNavItem>
<a ngbNavLink>{{'resource-policies.form.eperson-group-list.tab.group' | translate}}</a>
<ng-template ngbNavContent>
<ds-eperson-group-list [isListOfEPerson]="false"
(select)="updateObjectSelected($event, false)"></ds-eperson-group-list>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav"></div>
</ng-container>
<div>
<hr>
<div class="form-group row">

View File

@@ -3,6 +3,7 @@ import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserModule, By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { getTestScheduler } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
@@ -30,6 +31,14 @@ import { ResourcePolicy } from '../../../core/resource-policy/models/resource-po
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
import { EPersonMock } from '../../testing/eperson.mock';
import { isNotEmptyOperator } from '../../empty.util';
import { ActivatedRoute, Router } from '@angular/router';
import { RemoteData } from 'src/app/core/data/remote-data';
import { RouterMock } from '../../mocks/router.mock';
import { Store } from '@ngrx/store';
import { PaginationServiceStub } from '../../testing/pagination-service.stub';
import { PaginationService } from 'src/app/core/pagination/pagination.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { StoreMock } from '../../testing/store.mock';
export const mockResourcePolicyFormData = {
name: [
@@ -156,12 +165,23 @@ describe('ResourcePolicyFormComponent test suite', () => {
findAll: jasmine.createSpy('findAll')
});
const mockPolicyRD: RemoteData<ResourcePolicy> = createSuccessfulRemoteDataObject(resourcePolicy);
const activatedRouteStub = {
parent: {
data: observableOf({
dso: mockPolicyRD
})
}
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
BrowserModule,
CommonModule,
FormsModule,
NgbModule,
NoopAnimationsModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
@@ -172,9 +192,13 @@ describe('ResourcePolicyFormComponent test suite', () => {
TestComponent
],
providers: [
{ provide: ActivatedRoute, useValue: activatedRouteStub },
{ provide: Router, useValue: new RouterMock() },
{ provide: Store, useValue: StoreMock },
{ provide: EPersonDataService, useValue: epersonService },
{ provide: FormService, useValue: formService },
{ provide: GroupDataService, useValue: groupService },
{ provide: PaginationService, useValue: new PaginationServiceStub() },
{ provide: RequestService, useValue: getMockRequestService() },
FormBuilderService,
ChangeDetectorRef,

View File

@@ -54,12 +54,12 @@ export function getRequest(transferState: TransferState): any {
// forRoot ensures the providers are only created once
IdlePreloadModule.forRoot(),
RouterModule.forRoot([], {
// enableTracing: true,
useHash: false,
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
preloadingStrategy: NoPreloading
}),
// enableTracing: true,
useHash: false,
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
preloadingStrategy: NoPreloading
}),
StatisticsModule.forRoot(),
Angulartics2RouterlessModule.forRoot(),
BrowserAnimationsModule,

View File

@@ -59,3 +59,18 @@ ds-admin-sidebar {
display: none;
}
}
ngb-modal-backdrop {
// ng-bootsrap animates opacity, causing the fully opaque background to flash briefly before the transition starts
// animating background-color between transparent & a RGBA color instead looks smoother
&.fade {
opacity: 1 !important;
background-color: transparent;
transition: background-color 0.15s linear;
&.show {
background-color: rgba(0, 0, 0, 0.5);
}
}
}

2444
yarn.lock

File diff suppressed because it is too large Load Diff