mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'main' into w2p-74612_Ask-unauthenticated-users-to-log-in
Conflicts: src/app/+item-page/simple/item-page.component.ts src/app/process-page/detail/process-detail.component.spec.ts src/app/process-page/detail/process-detail.component.ts
This commit is contained in:
@@ -17,10 +17,9 @@ coverage:
|
||||
# Configuration for patch-level checks. This checks the relative coverage of the new PR code ONLY.
|
||||
patch:
|
||||
default:
|
||||
# For each PR, make sure the coverage of the new code is within 1% of current overall coverage.
|
||||
# We let 'patch' be more lenient as we only require *project* coverage to not drop significantly.
|
||||
target: auto
|
||||
threshold: 1%
|
||||
# Enable informational mode, which just provides info to reviewers & always passes
|
||||
# https://docs.codecov.io/docs/commit-status#section-informational
|
||||
informational: true
|
||||
|
||||
# Turn PR comments "off". This feature adds the code coverage summary as a
|
||||
# comment on each PR. See https://docs.codecov.io/docs/pull-request-comments
|
||||
|
@@ -12,3 +12,6 @@ trim_trailing_whitespace = true
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
@@ -40,7 +40,7 @@
|
||||
"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 && yarn run clean:env",
|
||||
"clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node",
|
||||
"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"
|
||||
},
|
||||
@@ -88,6 +88,7 @@
|
||||
"debug-loader": "^0.0.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"express": "4.16.2",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"fast-json-patch": "^2.0.7",
|
||||
"file-saver": "^1.3.8",
|
||||
"filesize": "^6.1.0",
|
||||
|
@@ -54,13 +54,6 @@ import(environmentFilePath)
|
||||
function generateEnvironmentFile(file: GlobalConfig): void {
|
||||
file.production = production;
|
||||
buildBaseUrls(file);
|
||||
|
||||
// TODO remove workaround in beta 5
|
||||
if (file.rest.nameSpace.match("(.*)/api/?$") !== null) {
|
||||
file.rest.nameSpace = getNameSpace(file.rest.nameSpace);
|
||||
console.log(colors.white.bgMagenta.bold(`The rest.nameSpace property in your environment file or in your DSPACE_REST_NAMESPACE environment variable ends with '/api'.\nThis is deprecated. As '/api' isn't configurable on the rest side, it shouldn't be repeated in every environment file.\nPlease change the rest nameSpace to '${file.rest.nameSpace}'`));
|
||||
}
|
||||
|
||||
const contents = `export const environment = ` + JSON.stringify(file);
|
||||
writeFile(targetPath, contents, (err) => {
|
||||
if (err) {
|
||||
@@ -119,16 +112,5 @@ function getPort(port: number): string {
|
||||
}
|
||||
|
||||
function getNameSpace(nameSpace: string): string {
|
||||
// TODO remove workaround in beta 5
|
||||
const apiMatches = nameSpace.match("(.*)/api/?$");
|
||||
if (apiMatches != null) {
|
||||
let newValue = '/'
|
||||
if (hasValue(apiMatches[1])) {
|
||||
newValue = apiMatches[1];
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
else {
|
||||
return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : '';
|
||||
}
|
||||
return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : '';
|
||||
}
|
||||
|
21
server.ts
21
server.ts
@@ -28,12 +28,13 @@ import * as compression from 'compression';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import { join } from 'path';
|
||||
|
||||
import { enableProdMode, NgModuleFactory, Type } from '@angular/core';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||
import { environment } from './src/environments/environment';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { hasValue, hasNoValue } from './src/app/shared/empty.util';
|
||||
import { hasNoValue, hasValue } from './src/app/shared/empty.util';
|
||||
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
||||
|
||||
/*
|
||||
* Set path for the browser application's dist folder
|
||||
@@ -121,6 +122,19 @@ function cacheControl(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the rateLimiter property is present
|
||||
* When it is present, the rateLimiter will be enabled. When it is undefined, the rateLimiter will be disabled.
|
||||
*/
|
||||
if (hasValue((environment.ui as UIServerConfig).rateLimiter)) {
|
||||
const RateLimit = require('express-rate-limit');
|
||||
const limiter = new RateLimit({
|
||||
windowMs: (environment.ui as UIServerConfig).rateLimiter.windowMs,
|
||||
max: (environment.ui as UIServerConfig).rateLimiter.max
|
||||
});
|
||||
app.use(limiter);
|
||||
}
|
||||
|
||||
/*
|
||||
* Serve static resources (images, i18n messages, …)
|
||||
*/
|
||||
@@ -209,8 +223,9 @@ if (environment.ui.ssl) {
|
||||
certificate: certificate
|
||||
});
|
||||
} else {
|
||||
console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.');
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
||||
|
||||
pem.createCertificate({
|
||||
days: 1,
|
||||
|
@@ -13,6 +13,7 @@ import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { getCollectionEditRoute } from '../../../../../+collection-page/collection-page-routing-paths';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
|
||||
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
let component: CollectionAdminSearchResultGridElementComponent;
|
||||
@@ -26,6 +27,11 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
searchResult.indexableObject = new Collection();
|
||||
searchResult.indexableObject.uuid = id;
|
||||
}
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: {}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
@@ -39,6 +45,7 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: BitstreamDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -14,8 +14,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { CommunityAdminSearchResultGridElementComponent } from './community-admin-search-result-grid-element.component';
|
||||
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
|
||||
import { Community } from '../../../../../core/shared/community.model';
|
||||
import { CommunityAdminSearchResultListElementComponent } from '../../admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
|
||||
import { getCommunityEditRoute } from '../../../../../+community-page/community-page-routing-paths';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
|
||||
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
let component: CommunityAdminSearchResultGridElementComponent;
|
||||
@@ -29,6 +29,11 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
searchResult.indexableObject = new Community();
|
||||
searchResult.indexableObject.uuid = id;
|
||||
}
|
||||
|
||||
const linkService = jasmine.createSpyObj('linkService', {
|
||||
resolveLink: {}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
@@ -42,6 +47,7 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: BitstreamDataService, useValue: {} },
|
||||
{ provide: LinkService, useValue: linkService}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
|
@@ -3,37 +3,41 @@
|
||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="collectionRD?.payload as collection">
|
||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||
<header class="comcol-header mr-auto">
|
||||
<!-- Collection Name -->
|
||||
<ds-comcol-page-header
|
||||
[name]="collection.name">
|
||||
</ds-comcol-page-header>
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
[logo]="(logoRD$ | async)?.payload"
|
||||
[alternateText]="'Collection Logo'"
|
||||
[alternateText]="'Collection Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<ds-comcol-page-header
|
||||
[name]="collection.name">
|
||||
</ds-comcol-page-header>
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
[logo]="(logoRD$ | async)?.payload"
|
||||
[alternateText]="'Collection Logo'"
|
||||
[alternateText]="'Collection Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle
|
||||
[content]="collection.handle"
|
||||
[title]="'collection.page.handle'" >
|
||||
</ds-comcol-page-handle>
|
||||
<!-- Introductory text -->
|
||||
<ds-comcol-page-content
|
||||
[content]="collection.introductoryText"
|
||||
[hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
<!-- News -->
|
||||
<ds-comcol-page-content
|
||||
[content]="collection.sidebarText"
|
||||
[hasInnerHtml]="true"
|
||||
[title]="'collection.page.news'">
|
||||
</ds-comcol-page-content>
|
||||
|
||||
</header>
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle
|
||||
[content]="collection.handle"
|
||||
[title]="'collection.page.handle'" >
|
||||
</ds-comcol-page-handle>
|
||||
<!-- Introductory text -->
|
||||
<ds-comcol-page-content
|
||||
[content]="collection.introductoryText"
|
||||
[hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
<!-- News -->
|
||||
<ds-comcol-page-content
|
||||
[content]="collection.sidebarText"
|
||||
[hasInnerHtml]="true"
|
||||
[title]="'collection.page.news'">
|
||||
</ds-comcol-page-content>
|
||||
</header>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'collections'" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="comcol-page-browse-section">
|
||||
<!-- Browse-By Links -->
|
||||
<ds-comcol-page-browse-by
|
||||
|
@@ -12,6 +12,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('CreateCollectionPageComponent', () => {
|
||||
let comp: CreateCollectionPageComponent;
|
||||
@@ -29,7 +30,8 @@ describe('CreateCollectionPageComponent', () => {
|
||||
},
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: RequestService, useValue: {}}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -7,6 +7,7 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Collection
|
||||
@@ -26,8 +27,9 @@ export class CreateCollectionPageComponent extends CreateComColPageComponent<Col
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(collectionDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
super(collectionDataService, communityDataService, routeService, router, notificationsService, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import { of as observableOf } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page.component';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('DeleteCollectionPageComponent', () => {
|
||||
let comp: DeleteCollectionPageComponent;
|
||||
@@ -22,6 +23,7 @@ describe('DeleteCollectionPageComponent', () => {
|
||||
{ provide: CollectionDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: NotificationsService, useValue: {} },
|
||||
{ provide: RequestService, useValue: {} }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -5,6 +5,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Collection
|
||||
@@ -22,8 +23,9 @@ export class DeleteCollectionPageComponent extends DeleteComColPageComponent<Col
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifications: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(dsoDataService, router, route, notifications, translate);
|
||||
super(dsoDataService, router, route, notifications, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ describe('CollectionCurateComponent', () => {
|
||||
let dsoNameService;
|
||||
|
||||
const collection = Object.assign(new Collection(), {
|
||||
handle: '123456789/1', metadata: {'dc.title': ['Collection Name']}
|
||||
metadata: {'dc.title': ['Collection Name'], 'dc.identifier.uri': [ { value: '123456789/1'}]}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
@@ -2,24 +2,28 @@
|
||||
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||
</ds-comcol-page-handle>
|
||||
<!-- Introductory text -->
|
||||
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
<!-- News -->
|
||||
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
||||
[title]="'community.page.news'">
|
||||
</ds-comcol-page-content>
|
||||
|
||||
</header>
|
||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||
<header class="comcol-header mr-auto">
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||
</ds-comcol-page-handle>
|
||||
<!-- Introductory text -->
|
||||
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
||||
</ds-comcol-page-content>
|
||||
<!-- News -->
|
||||
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
||||
[title]="'community.page.news'">
|
||||
</ds-comcol-page-content>
|
||||
</header>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'communities'" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="comcol-page-browse-section">
|
||||
<!-- Browse-By Links -->
|
||||
<ds-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
||||
|
@@ -12,6 +12,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCommunityPageComponent } from './create-community-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('CreateCommunityPageComponent', () => {
|
||||
let comp: CreateCommunityPageComponent;
|
||||
@@ -25,7 +26,8 @@ describe('CreateCommunityPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: { findById: () => observableOf({}) } },
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: RequestService, useValue: {} }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -6,6 +6,7 @@ import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Community
|
||||
@@ -24,8 +25,9 @@ export class CreateCommunityPageComponent extends CreateComColPageComponent<Comm
|
||||
protected routeService: RouteService,
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(communityDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
super(communityDataService, communityDataService, routeService, router, notificationsService, translate, requestService);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page.component';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
describe('DeleteCommunityPageComponent', () => {
|
||||
let comp: DeleteCommunityPageComponent;
|
||||
@@ -22,6 +23,7 @@ describe('DeleteCommunityPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: NotificationsService, useValue: {} },
|
||||
{ provide: RequestService, useValue: {}}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {RequestService} from '../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can delete an existing Community
|
||||
@@ -22,8 +23,10 @@ export class DeleteCommunityPageComponent extends DeleteComColPageComponent<Comm
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notifications: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
protected translate: TranslateService,
|
||||
protected requestService: RequestService
|
||||
) {
|
||||
super(dsoDataService, router, route, notifications, translate);
|
||||
super(dsoDataService, router, route, notifications, translate, requestService);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ describe('CommunityCurateComponent', () => {
|
||||
let dsoNameService;
|
||||
|
||||
const community = Object.assign(new Community(), {
|
||||
handle: '123456789/1', metadata: {'dc.title': ['Community Name']}
|
||||
metadata: {'dc.title': ['Community Name'], 'dc.identifier.uri': [ { value: '123456789/1'}]}
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
@@ -48,7 +48,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
ngOnInit(): void {
|
||||
observableCombineLatest(this.route.data, this.route.parent.data).pipe(
|
||||
map(([data, parentData]) => Object.assign({}, data, parentData)),
|
||||
map((data) => data.item),
|
||||
map((data) => data.dso),
|
||||
first(),
|
||||
map((data: RemoteData<Item>) => data.payload)
|
||||
).subscribe((item: Item) => {
|
||||
|
@@ -47,7 +47,7 @@ export class EditItemPageComponent implements OnInit {
|
||||
this.pages = this.route.routeConfig.children
|
||||
.map((child: any) => child.path)
|
||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -74,7 +74,7 @@ describe('ItemAuthorizationsComponent test suite', () => {
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject(item)
|
||||
dso: createSuccessfulRemoteDataObject(item)
|
||||
})
|
||||
};
|
||||
|
||||
|
@@ -75,7 +75,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.item$ = this.route.data.pipe(
|
||||
map((data) => data.item),
|
||||
map((data) => data.dso),
|
||||
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||
map((item: Item) => this.linkService.resolveLink(
|
||||
item,
|
||||
|
@@ -140,7 +140,7 @@ describe('ItemBitstreamsComponent', () => {
|
||||
});
|
||||
route = Object.assign({
|
||||
parent: {
|
||||
data: observableOf({ item: createMockRD(item) })
|
||||
data: observableOf({ dso: createMockRD(item) })
|
||||
},
|
||||
data: observableOf({}),
|
||||
url: url
|
||||
|
@@ -89,7 +89,7 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
clearDiscoveryRequests: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
});
|
||||
const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD });
|
||||
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockItemRD });
|
||||
const translateServiceStub = {
|
||||
get: () => of('test-message of item ' + mockItem.name),
|
||||
onLangChange: new EventEmitter(),
|
||||
|
@@ -92,7 +92,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||
this.loadCollectionLists();
|
||||
}
|
||||
|
@@ -138,7 +138,7 @@ describe('ItemDeleteComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject(mockItem)
|
||||
dso: createSuccessfulRemoteDataObject(mockItem)
|
||||
})
|
||||
};
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<span>{{metadata?.key?.split('.').join('.​')}}</span>
|
||||
</div>
|
||||
<div *ngIf="(editable | async)" class="field-container">
|
||||
<ds-filter-input-suggestions [suggestions]="(metadataFieldSuggestions | async)"
|
||||
<ds-validation-suggestions [suggestions]="(metadataFieldSuggestions | async)"
|
||||
[(ngModel)]="metadata.key"
|
||||
[url]="this.url"
|
||||
[metadata]="this.metadata"
|
||||
@@ -17,7 +17,7 @@
|
||||
[valid]="(valid | async) !== false"
|
||||
dsAutoFocus autoFocusSelector=".suggestion_input"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
></ds-filter-input-suggestions>
|
||||
></ds-validation-suggestions>
|
||||
</div>
|
||||
<small class="text-danger"
|
||||
*ngIf="(valid | async) === false">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small>
|
||||
|
@@ -20,9 +20,9 @@ import {
|
||||
} from '../../../../shared/remote-data.utils';
|
||||
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { EditInPlaceFieldComponent } from './edit-in-place-field.component';
|
||||
import { FilterInputSuggestionsComponent } from '../../../../shared/input-suggestions/filter-suggestions/filter-input-suggestions.component';
|
||||
import { MockComponent, MockDirective } from 'ng-mocks';
|
||||
import { DebounceDirective } from '../../../../shared/utils/debounce.directive';
|
||||
import { ValidationSuggestionsComponent } from '../../../../shared/input-suggestions/validation-suggestions/validation-suggestions.component';
|
||||
|
||||
let comp: EditInPlaceFieldComponent;
|
||||
let fixture: ComponentFixture<EditInPlaceFieldComponent>;
|
||||
@@ -88,7 +88,7 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
declarations: [
|
||||
EditInPlaceFieldComponent,
|
||||
MockDirective(DebounceDirective),
|
||||
MockComponent(FilterInputSuggestionsComponent)
|
||||
MockComponent(ValidationSuggestionsComponent)
|
||||
],
|
||||
providers: [
|
||||
{ provide: RegistryService, useValue: metadataFieldService },
|
||||
|
@@ -130,7 +130,7 @@ describe('ItemMetadataComponent', () => {
|
||||
routeStub = {
|
||||
data: observableOf({}),
|
||||
parent: {
|
||||
data: observableOf({ item: createSuccessfulRemoteDataObject(item) })
|
||||
data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
|
||||
}
|
||||
};
|
||||
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
|
||||
|
@@ -44,7 +44,7 @@ describe('ItemMoveComponent', () => {
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
item: new RemoteData(false, false, true, null, {
|
||||
dso: new RemoteData(false, false, true, null, {
|
||||
id: 'item1'
|
||||
})
|
||||
})
|
||||
|
@@ -55,7 +55,7 @@ export class ItemMoveComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item), getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso), getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemRD$.subscribe((rd) => {
|
||||
this.itemId = rd.payload.id;
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ describe('ItemPrivateComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
id: 'fake-id'
|
||||
})
|
||||
})
|
||||
|
@@ -51,7 +51,7 @@ describe('ItemPublicComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
id: 'fake-id'
|
||||
})
|
||||
})
|
||||
|
@@ -51,7 +51,7 @@ describe('ItemReinstateComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
id: 'fake-id'
|
||||
})
|
||||
})
|
||||
|
@@ -1,15 +1,26 @@
|
||||
<h5>{{getRelationshipMessageKey() | async | translate}}</h5>
|
||||
<h5>
|
||||
{{getRelationshipMessageKey() | async | translate}}
|
||||
<button class="ml-2 btn btn-success" (click)="openLookup()">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.relationships.edit.buttons.add" | translate}}</span>
|
||||
</button>
|
||||
</h5>
|
||||
<ng-container *ngVar="updates$ | async as updates">
|
||||
<ng-container *ngIf="updates">
|
||||
<ng-container *ngVar="updates | dsObjectValues as updateValues">
|
||||
<ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate"
|
||||
class="relationship-row d-block"
|
||||
[fieldUpdate]="updateValue"
|
||||
class="relationship-row d-block alert"
|
||||
[fieldUpdate]="updateValue || {}"
|
||||
[url]="url"
|
||||
[editItem]="item"
|
||||
[ngClass]="{'alert alert-danger': updateValue?.changeType === 2}">
|
||||
[ngClass]="{
|
||||
'alert-success': updateValue.changeType === 1,
|
||||
'alert-warning': updateValue.changeType === 0,
|
||||
'alert-danger': updateValue.changeType === 2
|
||||
}">
|
||||
</ds-edit-relationship>
|
||||
<div *ngIf="updateValues.length === 0">{{"item.edit.relationships.no-relationships" | translate}}</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<div *ngIf="!updates">no relationships</div>
|
||||
<ds-loading *ngIf="!updates"></ds-loading>
|
||||
</ng-container>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
.relationship-row:not(.alert-danger) {
|
||||
.relationship-row:not(.alert) {
|
||||
padding: $alert-padding-y 0;
|
||||
}
|
||||
|
||||
.relationship-row.alert-danger {
|
||||
.relationship-row.alert {
|
||||
margin-left: -$alert-padding-x;
|
||||
margin-right: -$alert-padding-x;
|
||||
margin-top: -1px;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
@@ -8,6 +8,7 @@ import { FieldChangeType } from '../../../../core/data/object-updates/object-upd
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||
@@ -15,6 +16,7 @@ import { Relationship } from '../../../../core/shared/item-relationships/relatio
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||
import { getMockLinkService } from '../../../../shared/mocks/link-service.mock';
|
||||
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { SharedModule } from '../../../../shared/shared.module';
|
||||
import { EditRelationshipListComponent } from './edit-relationship-list.component';
|
||||
|
||||
@@ -22,72 +24,123 @@ let comp: EditRelationshipListComponent;
|
||||
let fixture: ComponentFixture<EditRelationshipListComponent>;
|
||||
let de: DebugElement;
|
||||
|
||||
let linkService;
|
||||
let objectUpdatesService;
|
||||
let entityTypeService;
|
||||
let relationshipService;
|
||||
let selectableListService;
|
||||
|
||||
const url = 'http://test-url.com/test-url';
|
||||
|
||||
let item;
|
||||
let entityType;
|
||||
let relatedEntityType;
|
||||
let author1;
|
||||
let author2;
|
||||
let fieldUpdate1;
|
||||
let fieldUpdate2;
|
||||
let relationship1;
|
||||
let relationship2;
|
||||
let relationships;
|
||||
let relationshipType;
|
||||
let entityType;
|
||||
let relatedEntityType;
|
||||
|
||||
describe('EditRelationshipListComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async(() => {
|
||||
|
||||
entityType = Object.assign(new ItemType(), {
|
||||
id: 'entityType',
|
||||
id: 'Publication',
|
||||
uuid: 'Publication',
|
||||
label: 'Publication',
|
||||
});
|
||||
|
||||
relatedEntityType = Object.assign(new ItemType(), {
|
||||
id: 'relatedEntityType',
|
||||
id: 'Author',
|
||||
uuid: 'Author',
|
||||
label: 'Author',
|
||||
});
|
||||
|
||||
relationshipType = Object.assign(new RelationshipType(), {
|
||||
id: '1',
|
||||
uuid: '1',
|
||||
leftType: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
entityType,
|
||||
)),
|
||||
rightType: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
relatedEntityType,
|
||||
)),
|
||||
leftwardType: 'isAuthorOfPublication',
|
||||
rightwardType: 'isPublicationOfAuthor',
|
||||
leftType: observableOf(new RemoteData(false, false, true, undefined, entityType)),
|
||||
rightType: observableOf(new RemoteData(false, false, true, undefined, relatedEntityType)),
|
||||
});
|
||||
|
||||
relationship1 = Object.assign(new Relationship(), {
|
||||
_links: {
|
||||
self: {
|
||||
href: url + '/2'
|
||||
}
|
||||
},
|
||||
id: '2',
|
||||
uuid: '2',
|
||||
leftId: 'author1',
|
||||
rightId: 'publication',
|
||||
leftItem: observableOf(new RemoteData(false, false, true, undefined, item)),
|
||||
rightItem: observableOf(new RemoteData(false, false, true, undefined, author1)),
|
||||
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
||||
author1 = Object.assign(new Item(), {
|
||||
id: 'author1',
|
||||
uuid: 'author1'
|
||||
});
|
||||
author2 = Object.assign(new Item(), {
|
||||
id: 'author2',
|
||||
uuid: 'author2'
|
||||
});
|
||||
|
||||
relationship2 = Object.assign(new Relationship(), {
|
||||
_links: {
|
||||
self: {
|
||||
href: url + '/3'
|
||||
}
|
||||
},
|
||||
id: '3',
|
||||
uuid: '3',
|
||||
leftId: 'author2',
|
||||
rightId: 'publication',
|
||||
leftItem: observableOf(new RemoteData(false, false, true, undefined, item)),
|
||||
rightItem: observableOf(new RemoteData(false, false, true, undefined, author2)),
|
||||
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
||||
});
|
||||
relationships = [
|
||||
Object.assign(new Relationship(), {
|
||||
self: url + '/2',
|
||||
id: '2',
|
||||
uuid: '2',
|
||||
relationshipType: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
relationshipType
|
||||
)),
|
||||
leftItem: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
item,
|
||||
)),
|
||||
rightItem: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
author1,
|
||||
)),
|
||||
}),
|
||||
Object.assign(new Relationship(), {
|
||||
self: url + '/3',
|
||||
id: '3',
|
||||
uuid: '3',
|
||||
relationshipType: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
relationshipType
|
||||
)),
|
||||
leftItem: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
item,
|
||||
)),
|
||||
rightItem: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
author2,
|
||||
)),
|
||||
})
|
||||
];
|
||||
|
||||
item = Object.assign(new Item(), {
|
||||
_links: {
|
||||
@@ -100,84 +153,82 @@ describe('EditRelationshipListComponent', () => {
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
new PaginatedList(new PageInfo(), [relationship1, relationship2])
|
||||
new PaginatedList(new PageInfo(), relationships),
|
||||
))
|
||||
});
|
||||
|
||||
author1 = Object.assign(new Item(), {
|
||||
id: 'author1',
|
||||
uuid: 'author1'
|
||||
});
|
||||
author2 = Object.assign(new Item(), {
|
||||
id: 'author2',
|
||||
uuid: 'author2'
|
||||
});
|
||||
|
||||
fieldUpdate1 = {
|
||||
field: author1,
|
||||
field: {
|
||||
uuid: relationships[0].uuid,
|
||||
relationship: relationships[0],
|
||||
type: relationshipType,
|
||||
},
|
||||
changeType: undefined
|
||||
};
|
||||
fieldUpdate2 = {
|
||||
field: author2,
|
||||
field: {
|
||||
uuid: relationships[1].uuid,
|
||||
relationship: relationships[1],
|
||||
type: relationshipType,
|
||||
},
|
||||
changeType: FieldChangeType.REMOVE
|
||||
};
|
||||
|
||||
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
|
||||
{
|
||||
getFieldUpdates: observableOf({
|
||||
[author1.uuid]: fieldUpdate1,
|
||||
[author2.uuid]: fieldUpdate2
|
||||
[relationships[0].uuid]: fieldUpdate1,
|
||||
[relationships[1].uuid]: fieldUpdate2
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
entityTypeService = jasmine.createSpyObj('entityTypeService',
|
||||
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||
{
|
||||
getEntityTypeByLabel: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
entityType,
|
||||
)),
|
||||
getEntityTypeRelationships: observableOf(new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
new PaginatedList(new PageInfo(), [relationshipType]),
|
||||
)),
|
||||
getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))),
|
||||
getItemRelationshipsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), relationships))),
|
||||
isLeftItem: observableOf(true),
|
||||
}
|
||||
);
|
||||
|
||||
selectableListService = {};
|
||||
|
||||
linkService = {
|
||||
resolveLink: () => null,
|
||||
resolveLinks: () => null,
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedModule, TranslateModule.forRoot()],
|
||||
declarations: [EditRelationshipListComponent],
|
||||
providers: [
|
||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
||||
{ provide: RelationshipTypeService, useValue: {} },
|
||||
{ provide: LinkService, useValue: getMockLinkService() },
|
||||
{ provide: RelationshipService, useValue: relationshipService },
|
||||
{ provide: SelectableListService, useValue: selectableListService },
|
||||
{ provide: LinkService, useValue: linkService },
|
||||
], schemas: [
|
||||
NO_ERRORS_SCHEMA
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditRelationshipListComponent);
|
||||
comp = fixture.componentInstance;
|
||||
de = fixture.debugElement;
|
||||
|
||||
comp.item = item;
|
||||
comp.itemType = entityType;
|
||||
comp.url = url;
|
||||
comp.relationshipType = relationshipType;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('changeType is REMOVE', () => {
|
||||
it('the div should have class alert-danger', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fieldUpdate1.changeType = FieldChangeType.REMOVE;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('the div should have class alert-danger', () => {
|
||||
const element = de.queryAll(By.css('.relationship-row'))[1].nativeElement;
|
||||
expect(element.classList).toContain('alert-danger');
|
||||
});
|
||||
|
@@ -1,11 +1,23 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import {FieldUpdate, FieldUpdates} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates,
|
||||
RelationshipIdentifiable
|
||||
} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
import {Item} from '../../../../core/shared/item.model';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import {hasValue} from '../../../../shared/empty.util';
|
||||
import {
|
||||
defaultIfEmpty, filter, flatMap,
|
||||
map,
|
||||
switchMap,
|
||||
take, tap,
|
||||
} from 'rxjs/operators';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
|
||||
import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||
import {
|
||||
@@ -13,8 +25,13 @@ import {
|
||||
getRemoteDataPayload,
|
||||
getSucceededRemoteData
|
||||
} from '../../../../core/shared/operators';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, of } from 'rxjs';
|
||||
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
||||
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { SearchResult } from '../../../../shared/search/search-result.model';
|
||||
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||
|
||||
@Component({
|
||||
@@ -46,14 +63,29 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
*/
|
||||
@Input() relationshipType: RelationshipType;
|
||||
|
||||
private relatedEntityType$: Observable<ItemType>;
|
||||
|
||||
/**
|
||||
* The list ID to save selected entities under
|
||||
*/
|
||||
listId: string;
|
||||
|
||||
/**
|
||||
* The FieldUpdates for the relationships in question
|
||||
*/
|
||||
updates$: Observable<FieldUpdates>;
|
||||
|
||||
/**
|
||||
* A reference to the lookup window
|
||||
*/
|
||||
modalRef: NgbModalRef;
|
||||
|
||||
constructor(
|
||||
protected objectUpdatesService: ObjectUpdatesService,
|
||||
protected linkService: LinkService
|
||||
protected linkService: LinkService,
|
||||
protected relationshipService: RelationshipService,
|
||||
protected modalService: NgbModal,
|
||||
protected selectableListService: SelectableListService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -62,10 +94,18 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
*/
|
||||
public getRelationshipMessageKey(): Observable<string> {
|
||||
|
||||
return this.getLabel().pipe(
|
||||
map((label) => {
|
||||
if (hasValue(label) && label.indexOf('Of') > -1) {
|
||||
return `relationships.${label.substring(0, label.indexOf('Of') + 2)}`
|
||||
return observableCombineLatest(
|
||||
this.getLabel(),
|
||||
this.relatedEntityType$,
|
||||
).pipe(
|
||||
map(([label, relatedEntityType]) => {
|
||||
if (hasValue(label) && label.indexOf('is') > -1 && label.indexOf('Of') > -1) {
|
||||
const relationshipLabel = `${label.substring(2, label.indexOf('Of'))}`;
|
||||
if (relationshipLabel !== relatedEntityType.label) {
|
||||
return `relationships.is${relationshipLabel}Of.${relatedEntityType.label}`
|
||||
} else {
|
||||
return `relationships.is${relationshipLabel}Of`
|
||||
}
|
||||
} else {
|
||||
return label;
|
||||
}
|
||||
@@ -77,7 +117,6 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
* Get the relevant label for this relationship type
|
||||
*/
|
||||
private getLabel(): Observable<string> {
|
||||
|
||||
return observableCombineLatest([
|
||||
this.relationshipType.leftType,
|
||||
this.relationshipType.rightType,
|
||||
@@ -99,19 +138,197 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
return update && update.field ? update.field.uuid : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dynamic lookup modal to search for items to add as relationships
|
||||
*/
|
||||
openLookup() {
|
||||
|
||||
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
||||
size: 'lg'
|
||||
});
|
||||
const modalComp: DsDynamicLookupRelationModalComponent = this.modalRef.componentInstance;
|
||||
modalComp.repeatable = true;
|
||||
modalComp.listId = this.listId;
|
||||
modalComp.item = this.item;
|
||||
modalComp.select = (...selectableObjects: Array<SearchResult<Item>>) => {
|
||||
selectableObjects.forEach((searchResult) => {
|
||||
const relatedItem: Item = searchResult.indexableObject;
|
||||
this.getFieldUpdatesForRelatedItem(relatedItem)
|
||||
.subscribe((identifiables) => {
|
||||
identifiables.forEach((identifiable) =>
|
||||
this.objectUpdatesService.removeSingleFieldUpdate(this.url, identifiable.uuid)
|
||||
);
|
||||
if (identifiables.length === 0) {
|
||||
this.relationshipService.getNameVariant(this.listId, relatedItem.uuid)
|
||||
.subscribe((nameVariant) => {
|
||||
const update = {
|
||||
uuid: this.relationshipType.id + '-' + relatedItem.uuid,
|
||||
nameVariant,
|
||||
type: this.relationshipType,
|
||||
relatedItem,
|
||||
} as RelationshipIdentifiable;
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
modalComp.deselect = (...selectableObjects: Array<SearchResult<Item>>) => {
|
||||
selectableObjects.forEach((searchResult) => {
|
||||
const relatedItem: Item = searchResult.indexableObject;
|
||||
this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.relationshipType.id + '-' + relatedItem.uuid);
|
||||
this.getFieldUpdatesForRelatedItem(relatedItem)
|
||||
.subscribe((identifiables) =>
|
||||
identifiables.forEach((identifiable) =>
|
||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, identifiable)
|
||||
)
|
||||
);
|
||||
})
|
||||
};
|
||||
this.relatedEntityType$
|
||||
.pipe(take(1))
|
||||
.subscribe((relatedEntityType) => {
|
||||
modalComp.relationshipOptions = Object.assign(
|
||||
new RelationshipOptions(), {
|
||||
relationshipType: relatedEntityType.label,
|
||||
// filter: this.getRelationshipMessageKey(),
|
||||
searchConfiguration: relatedEntityType.label.toLowerCase(),
|
||||
nameVariants: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.selectableListService.deselectAll(this.listId);
|
||||
this.updates$.pipe(
|
||||
switchMap((updates) =>
|
||||
Object.values(updates).length > 0 ?
|
||||
observableCombineLatest(
|
||||
Object.values(updates)
|
||||
.filter((update) => update.changeType !== FieldChangeType.REMOVE)
|
||||
.map((update) => {
|
||||
const field = update.field as RelationshipIdentifiable;
|
||||
if (field.relationship) {
|
||||
return this.getRelatedItem(field.relationship);
|
||||
} else {
|
||||
return of(field.relatedItem);
|
||||
}
|
||||
})
|
||||
) : of([])
|
||||
),
|
||||
take(1),
|
||||
map((items) => items.map((item) => {
|
||||
const searchResult = new ItemSearchResult();
|
||||
searchResult.indexableObject = item;
|
||||
searchResult.hitHighlights = {};
|
||||
return searchResult;
|
||||
})),
|
||||
).subscribe((items) => {
|
||||
this.selectableListService.select(this.listId, items);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing field updates regarding a relationship with a given item
|
||||
* @param relatedItem The item for which to get the existing field updates
|
||||
*/
|
||||
private getFieldUpdatesForRelatedItem(relatedItem: Item): Observable<RelationshipIdentifiable[]> {
|
||||
|
||||
return this.updates$.pipe(
|
||||
take(1),
|
||||
map((updates) => Object.values(updates)
|
||||
.map((update) => update.field as RelationshipIdentifiable)
|
||||
.filter((field) => field.relationship)
|
||||
),
|
||||
flatMap((identifiables) =>
|
||||
observableCombineLatest(
|
||||
identifiables.map((identifiable) => this.getRelatedItem(identifiable.relationship))
|
||||
).pipe(
|
||||
defaultIfEmpty([]),
|
||||
map((relatedItems) =>
|
||||
identifiables.filter((identifiable, index) => relatedItems[index].uuid === relatedItem.uuid)
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related item for a given relationship
|
||||
* @param relationship The relationship for which to get the related item
|
||||
*/
|
||||
private getRelatedItem(relationship: Relationship): Observable<Item> {
|
||||
return this.relationshipService.isLeftItem(relationship, this.item).pipe(
|
||||
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
)
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.updates$ = this.item.relationships.pipe(
|
||||
|
||||
this.relatedEntityType$ =
|
||||
observableCombineLatest([
|
||||
this.relationshipType.leftType,
|
||||
this.relationshipType.rightType,
|
||||
].map((type) => type.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
))).pipe(
|
||||
map((relatedTypes) => relatedTypes.find((relatedType) => relatedType.uuid !== this.itemType.uuid)),
|
||||
);
|
||||
|
||||
this.relatedEntityType$.pipe(
|
||||
take(1)
|
||||
).subscribe(
|
||||
(relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}`
|
||||
);
|
||||
|
||||
this.updates$ = this.getItemRelationships().pipe(
|
||||
switchMap((relationships) =>
|
||||
observableCombineLatest(
|
||||
relationships.map((relationship) => this.relationshipService.isLeftItem(relationship, this.item))
|
||||
).pipe(
|
||||
defaultIfEmpty([]),
|
||||
map((isLeftItemArray) => isLeftItemArray.map((isLeftItem, index) => {
|
||||
const relationship = relationships[index];
|
||||
const nameVariant = isLeftItem ? relationship.rightwardValue : relationship.leftwardValue;
|
||||
return {
|
||||
uuid: relationship.id,
|
||||
type: this.relationshipType,
|
||||
relationship,
|
||||
nameVariant,
|
||||
} as RelationshipIdentifiable
|
||||
})),
|
||||
)),
|
||||
switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields).pipe(
|
||||
map((fieldUpdates) => {
|
||||
const fieldUpdatesFiltered: FieldUpdates = {};
|
||||
Object.keys(fieldUpdates).forEach((uuid) => {
|
||||
const field = fieldUpdates[uuid].field;
|
||||
if ((field as RelationshipIdentifiable).type.id === this.relationshipType.id) {
|
||||
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
|
||||
}
|
||||
});
|
||||
return fieldUpdatesFiltered;
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
private getItemRelationships() {
|
||||
this.linkService.resolveLink(this.item, followLink('relationships'));
|
||||
return this.item.relationships.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
map((relationships) => relationships.payload.page.filter((relationship) => relationship)),
|
||||
map((relationships: Relationship[]) =>
|
||||
relationships.map((relationship: Relationship) => {
|
||||
filter((relationships) => relationships.every((relationship) => !!relationship)),
|
||||
tap((relationships: Relationship[]) =>
|
||||
relationships.forEach((relationship: Relationship) => {
|
||||
this.linkService.resolveLinks(
|
||||
relationship,
|
||||
followLink('relationshipType'),
|
||||
followLink('leftItem'),
|
||||
followLink('rightItem'),
|
||||
);
|
||||
return relationship;
|
||||
})
|
||||
),
|
||||
switchMap((itemRelationships: Relationship[]) =>
|
||||
@@ -122,15 +339,12 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
getRemoteDataPayload(),
|
||||
))
|
||||
).pipe(
|
||||
defaultIfEmpty([]),
|
||||
map((relationshipTypes) => itemRelationships.filter(
|
||||
(relationship, index) => relationshipTypes[index].id === this.relationshipType.id)
|
||||
),
|
||||
map((relationships) => relationships.map((relationship) =>
|
||||
Object.assign(new Relationship(), relationship, {uuid: relationship.id})
|
||||
)),
|
||||
)
|
||||
),
|
||||
switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<div class="row" *ngIf="relatedItem$ | async">
|
||||
<div class="col-10 relationship">
|
||||
<ds-listable-object-component-loader [object]="relatedItem$ | async" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||
<ds-listable-object-component-loader
|
||||
[object]="relatedItem$ | async"
|
||||
[viewMode]="viewMode"
|
||||
[value]="nameVariant"
|
||||
>
|
||||
</ds-listable-object-component-loader>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="btn-group relationship-action-buttons">
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||
@@ -25,7 +25,7 @@ let fieldUpdate2;
|
||||
let relationships;
|
||||
let relationshipType;
|
||||
|
||||
let fixture;
|
||||
let fixture: ComponentFixture<EditRelationshipComponent>;
|
||||
let comp: EditRelationshipComponent;
|
||||
let de;
|
||||
let el;
|
||||
@@ -91,11 +91,17 @@ describe('EditRelationshipComponent', () => {
|
||||
});
|
||||
|
||||
fieldUpdate1 = {
|
||||
field: relationships[0],
|
||||
field: {
|
||||
uuid: relationships[0].uuid,
|
||||
relationship: relationships[0],
|
||||
},
|
||||
changeType: undefined
|
||||
};
|
||||
fieldUpdate2 = {
|
||||
field: relationships[1],
|
||||
field: {
|
||||
uuid: relationships[1].uuid,
|
||||
relationship: relationships[1],
|
||||
},
|
||||
changeType: FieldChangeType.REMOVE
|
||||
};
|
||||
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, Observable, of } from 'rxjs';
|
||||
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||
import { DeleteRelationship, FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import {
|
||||
DeleteRelationship,
|
||||
FieldUpdate,
|
||||
RelationshipIdentifiable
|
||||
} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
@@ -36,8 +39,16 @@ export class EditRelationshipComponent implements OnChanges {
|
||||
/**
|
||||
* The relationship being edited
|
||||
*/
|
||||
get relationship(): Relationship {
|
||||
return this.fieldUpdate.field as Relationship;
|
||||
get relationship() {
|
||||
return this.update.relationship;
|
||||
}
|
||||
|
||||
get update() {
|
||||
return this.fieldUpdate.field as RelationshipIdentifiable;
|
||||
}
|
||||
|
||||
get nameVariant() {
|
||||
return this.update.nameVariant;
|
||||
}
|
||||
|
||||
public leftItem$: Observable<Item>;
|
||||
@@ -68,24 +79,28 @@ export class EditRelationshipComponent implements OnChanges {
|
||||
* Sets the current relationship based on the fieldUpdate input field
|
||||
*/
|
||||
ngOnChanges(): void {
|
||||
this.leftItem$ = this.relationship.leftItem.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
|
||||
);
|
||||
this.rightItem$ = this.relationship.rightItem.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
|
||||
);
|
||||
this.relatedItem$ = observableCombineLatest(
|
||||
this.leftItem$,
|
||||
this.rightItem$,
|
||||
).pipe(
|
||||
map((items: Item[]) =>
|
||||
items.find((item) => item.uuid !== this.editItem.uuid)
|
||||
)
|
||||
);
|
||||
if (this.relationship) {
|
||||
this.leftItem$ = this.relationship.leftItem.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
|
||||
);
|
||||
this.rightItem$ = this.relationship.rightItem.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
|
||||
);
|
||||
this.relatedItem$ = observableCombineLatest(
|
||||
this.leftItem$,
|
||||
this.rightItem$,
|
||||
).pipe(
|
||||
map((items: Item[]) =>
|
||||
items.find((item) => item.uuid !== this.editItem.uuid)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
this.relatedItem$ = of(this.update.relatedItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +151,8 @@ export class EditRelationshipComponent implements OnChanges {
|
||||
* Check if a user should be allowed to remove this field
|
||||
*/
|
||||
canRemove(): boolean {
|
||||
return this.fieldUpdate.changeType !== FieldChangeType.REMOVE;
|
||||
return this.fieldUpdate.changeType !== FieldChangeType.REMOVE
|
||||
&& this.fieldUpdate.changeType !== FieldChangeType.ADD;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -19,14 +19,19 @@
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngFor="let relationshipType of relationshipTypes$ | async" class="mb-4">
|
||||
<ds-edit-relationship-list
|
||||
[url]="url"
|
||||
[item]="item"
|
||||
[itemType]="entityType"
|
||||
[relationshipType]="relationshipType"
|
||||
></ds-edit-relationship-list>
|
||||
</div>
|
||||
<ng-container *ngVar="relationshipTypes$ | async as relationshipTypes">
|
||||
<ng-container *ngIf="relationshipTypes">
|
||||
<div *ngFor="let relationshipType of relationshipTypes" class="mb-4">
|
||||
<ds-edit-relationship-list
|
||||
[url]="url"
|
||||
[item]="item"
|
||||
[itemType]="entityType$ | async"
|
||||
[relationshipType]="relationshipType"
|
||||
></ds-edit-relationship-list>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ds-loading *ngIf="!relationshipTypes"></ds-loading>
|
||||
</ng-container>
|
||||
<div class="button-row bottom">
|
||||
<div class="float-right">
|
||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
|
@@ -142,7 +142,7 @@ describe('ItemRelationshipsComponent', () => {
|
||||
routeStub = {
|
||||
data: observableOf({}),
|
||||
parent: {
|
||||
data: observableOf({ item: new RemoteData(false, false, true, null, item) })
|
||||
data: observableOf({ dso: new RemoteData(false, false, true, null, item) })
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { DeleteRelationship, FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import {
|
||||
DeleteRelationship,
|
||||
FieldUpdate,
|
||||
FieldUpdates,
|
||||
RelationshipIdentifiable,
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { of as observableOf, zip as observableZip} from 'rxjs';
|
||||
import { filter, map, startWith, switchMap, take} from 'rxjs/operators';
|
||||
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip} from 'rxjs';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
@@ -17,11 +22,9 @@ import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
||||
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||
import { isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||
|
||||
@@ -33,18 +36,18 @@ import { Relationship } from '../../../core/shared/item-relationships/relationsh
|
||||
/**
|
||||
* Component for displaying an item's relationships edit page
|
||||
*/
|
||||
export class ItemRelationshipsComponent extends AbstractItemUpdateComponent implements OnDestroy {
|
||||
export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The labels of all different relations within this item
|
||||
* The allowed relationship types for this type of item as an observable list
|
||||
*/
|
||||
relationshipTypes$: Observable<RelationshipType[]>;
|
||||
|
||||
/**
|
||||
* A subscription that checks when the item is deleted in cache and reloads the item by sending a new request
|
||||
* This is used to update the item in cache after relationships are deleted
|
||||
* The item's entity type as an observable
|
||||
*/
|
||||
itemUpdateSubscription: Subscription;
|
||||
entityType$: Observable<ItemType>;
|
||||
|
||||
constructor(
|
||||
@@ -68,15 +71,29 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe(
|
||||
this.initializeItemUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the item (and view) when it's removed in the request cache
|
||||
*/
|
||||
public initializeItemUpdate(): void {
|
||||
this.itemRD$ = this.requestService.hasByHrefObservable(this.item.self).pipe(
|
||||
filter((exists: boolean) => !exists),
|
||||
switchMap(() => this.itemService.findById(this.item.uuid,
|
||||
switchMap(() => this.itemService.findById(
|
||||
this.item.uuid,
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles'),
|
||||
followLink('relationships'))),
|
||||
followLink('relationships')),
|
||||
),
|
||||
filter((itemRD) => !!itemRD.statusCode),
|
||||
);
|
||||
|
||||
this.itemRD$.pipe(
|
||||
getSucceededRemoteData(),
|
||||
).subscribe((itemRD: RemoteData<Item>) => {
|
||||
this.item = itemRD.payload;
|
||||
getRemoteDataPayload(),
|
||||
).subscribe((item) => {
|
||||
this.item = item;
|
||||
this.cdr.detectChanges();
|
||||
this.initializeUpdates();
|
||||
});
|
||||
@@ -125,10 +142,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
* Make sure the lists are refreshed afterwards and notifications are sent for success and errors
|
||||
*/
|
||||
public submit(): void {
|
||||
|
||||
// Get all the relationships that should be removed
|
||||
this.relationshipService.getItemRelationshipsArray(this.item).pipe(
|
||||
const removedRelationshipIDs$: Observable<DeleteRelationship[]> = this.relationshipService.getItemRelationshipsArray(this.item).pipe(
|
||||
startWith([]),
|
||||
map((relationships: Relationship[]) => relationships.map((relationship) =>
|
||||
Object.assign(new Relationship(), relationship, {uuid: relationship.id})
|
||||
Object.assign(new Relationship(), relationship, { uuid: relationship.id })
|
||||
)),
|
||||
switchMap((relationships: Relationship[]) => {
|
||||
return this.objectUpdatesService.getFieldUpdatesExclusive(this.url, relationships) as Observable<FieldUpdates>
|
||||
@@ -138,31 +157,83 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
.filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)
|
||||
.map((fieldUpdate: FieldUpdate) => fieldUpdate.field as DeleteRelationship)
|
||||
),
|
||||
isNotEmptyOperator(),
|
||||
take(1),
|
||||
switchMap((deleteRelationships: DeleteRelationship[]) =>
|
||||
observableZip(...deleteRelationships.map((deleteRelationship) => {
|
||||
let copyVirtualMetadata: string;
|
||||
if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) {
|
||||
copyVirtualMetadata = 'all';
|
||||
} else if (deleteRelationship.keepLeftVirtualMetadata) {
|
||||
copyVirtualMetadata = 'left';
|
||||
} else if (deleteRelationship.keepRightVirtualMetadata) {
|
||||
copyVirtualMetadata = 'right';
|
||||
} else {
|
||||
copyVirtualMetadata = 'none';
|
||||
}
|
||||
return this.relationshipService.deleteRelationship(deleteRelationship.uuid, copyVirtualMetadata);
|
||||
}
|
||||
))
|
||||
);
|
||||
|
||||
const addRelatedItems$: Observable<RelationshipIdentifiable[]> = this.objectUpdatesService.getFieldUpdates(this.url, []).pipe(
|
||||
map((fieldUpdates: FieldUpdates) =>
|
||||
Object.values(fieldUpdates)
|
||||
.filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD)
|
||||
.map((fieldUpdate: FieldUpdate) => fieldUpdate.field as RelationshipIdentifiable)
|
||||
),
|
||||
).subscribe((responses: RestResponse[]) => {
|
||||
this.itemUpdateSubscription.add(() => {
|
||||
this.displayNotifications(responses);
|
||||
});
|
||||
);
|
||||
|
||||
observableCombineLatest(
|
||||
removedRelationshipIDs$,
|
||||
addRelatedItems$,
|
||||
).pipe(
|
||||
take(1),
|
||||
).subscribe(([removeRelationshipIDs, addRelatedItems]) => {
|
||||
const actions = [
|
||||
this.deleteRelationships(removeRelationshipIDs),
|
||||
this.addRelationships(addRelatedItems),
|
||||
];
|
||||
actions.forEach((action) =>
|
||||
action.subscribe((response) => {
|
||||
if (response.length > 0) {
|
||||
this.itemRD$.subscribe(() => {
|
||||
this.initializeOriginalFields();
|
||||
this.cdr.detectChanges();
|
||||
this.displayNotifications(response);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
deleteRelationships(deleteRelationshipIDs: DeleteRelationship[]): Observable<RestResponse[]> {
|
||||
return observableZip(...deleteRelationshipIDs.map((deleteRelationship) => {
|
||||
let copyVirtualMetadata: string;
|
||||
if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) {
|
||||
copyVirtualMetadata = 'all';
|
||||
} else if (deleteRelationship.keepLeftVirtualMetadata) {
|
||||
copyVirtualMetadata = 'left';
|
||||
} else if (deleteRelationship.keepRightVirtualMetadata) {
|
||||
copyVirtualMetadata = 'right';
|
||||
} else {
|
||||
copyVirtualMetadata = 'none';
|
||||
}
|
||||
return this.relationshipService.deleteRelationship(deleteRelationship.uuid, copyVirtualMetadata);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
addRelationships(addRelatedItems: RelationshipIdentifiable[]): Observable<RestResponse[]> {
|
||||
return observableZip(...addRelatedItems.map((addRelationship) =>
|
||||
this.entityType$.pipe(
|
||||
switchMap((entityType) => this.entityTypeService.isLeftType(addRelationship.type, entityType)),
|
||||
switchMap((isLeftType) => {
|
||||
let leftItem: Item;
|
||||
let rightItem: Item;
|
||||
let leftwardValue: string;
|
||||
let rightwardValue: string;
|
||||
if (isLeftType) {
|
||||
leftItem = this.item;
|
||||
rightItem = addRelationship.relatedItem;
|
||||
leftwardValue = null;
|
||||
rightwardValue = addRelationship.nameVariant;
|
||||
} else {
|
||||
leftItem = addRelationship.relatedItem;
|
||||
rightItem = this.item;
|
||||
leftwardValue = addRelationship.nameVariant;
|
||||
rightwardValue = null;
|
||||
}
|
||||
return this.relationshipService.addRelationship(addRelationship.type.id, leftItem, rightItem, leftwardValue, rightwardValue);
|
||||
}),
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display notifications
|
||||
* - Error notification for each failed response with their message
|
||||
@@ -180,19 +251,14 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all initial values of this item to the object updates service
|
||||
*/
|
||||
public initializeOriginalFields() {
|
||||
const initialFields = [];
|
||||
this.objectUpdatesService.initialize(this.url, initialFields, this.item.lastModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the item update when the component is destroyed
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.itemUpdateSubscription.unsubscribe();
|
||||
return this.relationshipService.getRelatedItems(this.item).pipe(
|
||||
take(1),
|
||||
).subscribe((items: Item[]) => {
|
||||
this.objectUpdatesService.initialize(this.url, items, this.item.lastModified);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ describe('ItemStatusComponent', () => {
|
||||
|
||||
const routeStub = {
|
||||
parent: {
|
||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
||||
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -56,7 +56,7 @@ export class ItemStatusComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item));
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso));
|
||||
this.itemRD$.pipe(
|
||||
first(),
|
||||
map((data: RemoteData<Item>) => data.payload)
|
||||
|
@@ -23,7 +23,7 @@ describe('ItemVersionHistoryComponent', () => {
|
||||
declarations: [ItemVersionHistoryComponent, VarDirective],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ item: createSuccessfulRemoteDataObject(item) }) } } }
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(item) }) } } }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -30,6 +30,6 @@ export class ItemVersionHistoryComponent {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
}
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ describe('ItemWithdrawComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
id: 'fake-id'
|
||||
})
|
||||
})
|
||||
|
@@ -74,7 +74,7 @@ describe('AbstractSimpleItemActionComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
item: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
id: 'fake-id'
|
||||
})
|
||||
})
|
||||
|
@@ -42,7 +42,7 @@ export class AbstractSimpleItemActionComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(
|
||||
map((data) => data.item),
|
||||
map((data) => data.dso),
|
||||
getSucceededRemoteData()
|
||||
)as Observable<RemoteData<Item>>;
|
||||
|
||||
|
@@ -3,7 +3,12 @@
|
||||
<div *ngIf="itemRD?.payload as item">
|
||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
||||
<div class="d-flex flex-row">
|
||||
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="simple-view-link my-3">
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||
{{"item.page.link.simple" | translate}}
|
||||
|
@@ -13,7 +13,6 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { By } from '@angular/platform-browser';
|
||||
@@ -35,7 +34,7 @@ const mockItem: Item = Object.assign(new Item(), {
|
||||
}
|
||||
});
|
||||
const routeStub = Object.assign(new ActivatedRouteStub(), {
|
||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
||||
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||
});
|
||||
const metadataServiceStub = {
|
||||
/* tslint:disable:no-empty */
|
||||
|
@@ -20,7 +20,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
{
|
||||
path: ':id',
|
||||
resolve: {
|
||||
item: ItemPageResolver,
|
||||
dso: ItemPageResolver,
|
||||
breadcrumb: ItemBreadcrumbResolver
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
|
@@ -8,7 +8,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
@@ -39,7 +38,7 @@ describe('ItemPageComponent', () => {
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
const mockRoute = Object.assign(new ActivatedRouteStub(), {
|
||||
data: observableOf({ item: createSuccessfulRemoteDataObject(mockItem) })
|
||||
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
@@ -57,7 +57,7 @@ export class ItemPageComponent implements OnInit {
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(
|
||||
map((data) => data.item as RemoteData<Item>),
|
||||
map((data) => data.dso as RemoteData<Item>),
|
||||
redirectOn4xx(this.router, this.authService)
|
||||
);
|
||||
this.metadataService.processRemoteData(this.itemRD$);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<h2 class="item-page-title-field">
|
||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="d-flex flex-row">
|
||||
<h2 class="item-page-title-field mr-auto">
|
||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'Home', url: '/'}"></ng-container>
|
||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
|
||||
<ng-container *ngFor="let bc of breadcrumbs; let last = last;">
|
||||
<ng-container *ngTemplateOutlet="!last ? breadcrumb : activeBreadcrumb; context: bc"></ng-container>
|
||||
</ng-container>
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { NgZone } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { CommunityListService, FlatNode } from './community-list-service';
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
|
||||
import { BehaviorSubject, Observable, } from 'rxjs';
|
||||
import { finalize, take, } from 'rxjs/operators';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* DataSource object needed by a CDK Tree to render its nodes.
|
||||
@@ -15,9 +16,9 @@ export class CommunityListDatasource implements DataSource<FlatNode> {
|
||||
|
||||
private communityList$ = new BehaviorSubject<FlatNode[]>([]);
|
||||
public loading$ = new BehaviorSubject<boolean>(false);
|
||||
private subLoadCommunities: Subscription;
|
||||
|
||||
constructor(private communityListService: CommunityListService,
|
||||
private zone: NgZone) {
|
||||
constructor(private communityListService: CommunityListService) {
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
|
||||
@@ -26,13 +27,13 @@ export class CommunityListDatasource implements DataSource<FlatNode> {
|
||||
|
||||
loadCommunities(findOptions: FindListOptions, expandedNodes: FlatNode[]) {
|
||||
this.loading$.next(true);
|
||||
this.zone.runOutsideAngular(() => {
|
||||
this.communityListService.loadCommunities(findOptions, expandedNodes).pipe(
|
||||
take(1),
|
||||
finalize(() => this.zone.run(() => this.loading$.next(false))),
|
||||
).subscribe((flatNodes: FlatNode[]) => {
|
||||
this.zone.run(() => this.communityList$.next(flatNodes));
|
||||
});
|
||||
if (hasValue(this.subLoadCommunities)) {
|
||||
this.subLoadCommunities.unsubscribe();
|
||||
}
|
||||
this.subLoadCommunities = this.communityListService.loadCommunities(findOptions, expandedNodes).pipe(
|
||||
finalize(() => this.loading$.next(false)),
|
||||
).subscribe((flatNodes: FlatNode[]) => {
|
||||
this.communityList$.next(flatNodes);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,13 +2,12 @@ import { Injectable } from '@angular/core';
|
||||
import { createSelector, Store } from '@ngrx/store';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { map, flatMap } from 'rxjs/operators';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||
import { PageInfo } from '../core/shared/page-info.model';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
@@ -148,7 +147,7 @@ export class CommunityListService {
|
||||
return new PaginatedList(newPageInfo, newPage);
|
||||
})
|
||||
);
|
||||
return topComs$.pipe(flatMap((topComs: PaginatedList<Community>) => this.transformListOfCommunities(topComs, 0, null, expandedNodes)));
|
||||
return topComs$.pipe(switchMap((topComs: PaginatedList<Community>) => this.transformListOfCommunities(topComs, 0, null, expandedNodes)));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,9 +227,13 @@ export class CommunityListService {
|
||||
currentPage: i
|
||||
})
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
flatMap((rd: RemoteData<PaginatedList<Community>>) =>
|
||||
this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes))
|
||||
switchMap((rd: RemoteData<PaginatedList<Community>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
subcoms = [...subcoms, nextSetOfSubcommunitiesPage];
|
||||
@@ -246,14 +249,17 @@ export class CommunityListService {
|
||||
currentPage: i
|
||||
})
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
||||
let nodes = rd.payload.page
|
||||
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
|
||||
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
|
||||
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
let nodes = rd.payload.page
|
||||
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
|
||||
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
|
||||
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
|
||||
}
|
||||
return nodes;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
return nodes;
|
||||
}),
|
||||
);
|
||||
collections = [...collections, nextSetOfCollectionsPage];
|
||||
@@ -275,14 +281,24 @@ export class CommunityListService {
|
||||
let hasColls$: Observable<boolean>;
|
||||
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((results) => results.payload.totalElements > 0),
|
||||
map((rd: RemoteData<PaginatedList<Community>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return rd.payload.totalElements > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||
.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((results) => results.payload.totalElements > 0),
|
||||
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
||||
return rd.payload.totalElements > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let hasChildren$: Observable<boolean>;
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<button type="button" class="btn btn-default" cdkTreeNodeToggle
|
||||
[attr.aria-label]="'toggle ' + node.name"
|
||||
(click)="toggleExpanded(node)"
|
||||
[ngClass]="(node.isExpandable$ | async) ? 'visible' : 'invisible'">
|
||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'">
|
||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { FindListOptions } from '../../core/data/request.models';
|
||||
@@ -24,15 +24,14 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
public loadingNode: FlatNode;
|
||||
|
||||
treeControl = new FlatTreeControl<FlatNode>(
|
||||
(node) => node.level, (node) => true
|
||||
(node: FlatNode) => node.level, (node: FlatNode) => true
|
||||
);
|
||||
|
||||
dataSource: CommunityListDatasource;
|
||||
|
||||
paginationConfig: FindListOptions;
|
||||
|
||||
constructor(private communityListService: CommunityListService,
|
||||
private zone: NgZone) {
|
||||
constructor(private communityListService: CommunityListService) {
|
||||
this.paginationConfig = new FindListOptions();
|
||||
this.paginationConfig.elementsPerPage = 2;
|
||||
this.paginationConfig.currentPage = 1;
|
||||
@@ -40,7 +39,7 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource = new CommunityListDatasource(this.communityListService, this.zone);
|
||||
this.dataSource = new CommunityListDatasource(this.communityListService);
|
||||
this.communityListService.getLoadingNodeFromStore().pipe(take(1)).subscribe((result) => {
|
||||
this.loadingNode = result;
|
||||
});
|
||||
@@ -65,7 +64,7 @@ export class CommunityListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the expanded variable of a node, adds it to the exapanded nodes list and reloads the tree so this node is expanded
|
||||
* Toggles the expanded variable of a node, adds it to the expanded nodes list and reloads the tree so this node is expanded
|
||||
* @param node Node we want to expand
|
||||
*/
|
||||
toggleExpanded(node: FlatNode) {
|
||||
|
@@ -6,7 +6,6 @@ import { HALLink } from '../../shared/hal-link.model';
|
||||
import { HALResource } from '../../shared/hal-resource.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import * as decorators from './build-decorators';
|
||||
import { getDataServiceFor } from './build-decorators';
|
||||
import { LinkService } from './link.service';
|
||||
|
||||
const spyOnFunction = <T>(obj: T, func: keyof T) => {
|
||||
|
2
src/app/core/cache/builders/link.service.ts
vendored
2
src/app/core/cache/builders/link.service.ts
vendored
@@ -27,7 +27,7 @@ export class LinkService {
|
||||
*/
|
||||
public resolveLinks<T extends HALResource>(model: T, ...linksToFollow: Array<FollowLinkConfig<T>>): T {
|
||||
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
|
||||
this.resolveLink(model, linkToFollow);
|
||||
this.resolveLink(model, linkToFollow);
|
||||
});
|
||||
return model;
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { FindByIDRequest, FindListOptions } from './request.models';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestService } from './request.service';
|
||||
import {createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils';
|
||||
|
||||
const LINK_NAME = 'test';
|
||||
|
||||
@@ -51,7 +52,9 @@ describe('ComColDataService', () => {
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: any = {};
|
||||
|
||||
const rdbService = {} as RemoteDataBuildService;
|
||||
const rdbService = {
|
||||
buildSingle : () => null
|
||||
} as any;
|
||||
const store = {} as Store<CoreState>;
|
||||
const notificationsService = {} as NotificationsService;
|
||||
const http = {} as HttpClient;
|
||||
@@ -178,6 +181,90 @@ describe('ComColDataService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache refresh', () => {
|
||||
let communityWithoutParentHref;
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
halService = {
|
||||
getEndpoint: (linkPath) => 'https://rest.api/core/' + linkPath
|
||||
};
|
||||
service = initTestService();
|
||||
|
||||
})
|
||||
describe('cache refreshed top level community', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(rdbService, 'buildSingle').and.returnValue(createNoContentRemoteDataObject$());
|
||||
data = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'top level community'
|
||||
}]
|
||||
}),
|
||||
_links: {
|
||||
parentCommunity: {
|
||||
href: 'topLevel/parentCommunity'
|
||||
}
|
||||
}
|
||||
};
|
||||
communityWithoutParentHref = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'top level community'
|
||||
}]
|
||||
}),
|
||||
_links: {}
|
||||
};
|
||||
});
|
||||
it('top level community cache refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('https://rest.api/core/communities/search/top');
|
||||
});
|
||||
it('top level community without parent link, cache not refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(communityWithoutParentHref));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache refreshed child community', () => {
|
||||
beforeEach(() => {
|
||||
const parentCommunity = Object.assign(new Community(), {
|
||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'parent community'
|
||||
}],
|
||||
_links: {}
|
||||
});
|
||||
spyOn(rdbService, 'buildSingle').and.returnValue(createSuccessfulRemoteDataObject$(parentCommunity));
|
||||
data = {
|
||||
dso: Object.assign(new Community(), {
|
||||
metadata: [{
|
||||
key: 'dc.title',
|
||||
value: 'child community'
|
||||
}]
|
||||
}),
|
||||
_links: {
|
||||
parentCommunity: {
|
||||
href: 'child/parentCommunity'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
it('child level community cache refreshed', () => {
|
||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
||||
scheduler.flush();
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -21,12 +21,14 @@ import {
|
||||
configureRequest,
|
||||
getRemoteDataPayload,
|
||||
getResponseFromEntry,
|
||||
getSucceededOrNoContentResponse,
|
||||
getSucceededRemoteData
|
||||
} from '../shared/operators';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import {Collection} from '../shared/collection.model';
|
||||
|
||||
export abstract class ComColDataService<T extends CacheableObject> extends DataService<T> {
|
||||
protected abstract cds: CommunityDataService;
|
||||
@@ -119,4 +121,23 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public refreshCache(dso: T) {
|
||||
const parentCommunityUrl = this.parentCommunityUrlLookup(dso as any);
|
||||
if (!hasValue(parentCommunityUrl)) {
|
||||
return;
|
||||
}
|
||||
this.findByHref(parentCommunityUrl).pipe(
|
||||
getSucceededOrNoContentResponse(),
|
||||
take(1),
|
||||
).subscribe((rd: RemoteData<any>) => {
|
||||
const href = rd.hasSucceeded && !isEmpty(rd.payload.id) ? rd.payload.id : this.halService.getEndpoint('communities/search/top');
|
||||
this.requestService.removeByHrefSubstring(href)
|
||||
});
|
||||
}
|
||||
|
||||
private parentCommunityUrlLookup(dso: Collection | Community) {
|
||||
const parentCommunity = dso._links.parentCommunity;
|
||||
return isNotEmpty(parentCommunity) ? parentCommunity.href : null;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { take, tap } from 'rxjs/operators';
|
||||
import { filter, take, tap } from 'rxjs/operators';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
@@ -11,7 +11,6 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { getFinishedRemoteData } from '../shared/operators';
|
||||
import { DataService } from './data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { RemoteData } from './remote-data';
|
||||
@@ -56,7 +55,7 @@ export class DsoRedirectDataService extends DataService<any> {
|
||||
findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<FindByIDRequest>> {
|
||||
this.setLinkPath(identifierType);
|
||||
return this.findById(id).pipe(
|
||||
getFinishedRemoteData(),
|
||||
filter((response) => hasValue(response.error) || hasValue(response.payload)),
|
||||
take(1),
|
||||
tap((response) => {
|
||||
if (response.hasSucceeded) {
|
||||
|
@@ -12,11 +12,12 @@ import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GetRequest } from './request.models';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import {switchMap, take, tap} from 'rxjs/operators';
|
||||
import {switchMap, take, map} from 'rxjs/operators';
|
||||
import { RemoteData } from './remote-data';
|
||||
import {RelationshipType} from '../shared/item-relationships/relationship-type.model';
|
||||
import {PaginatedList} from './paginated-list';
|
||||
import {ItemType} from '../shared/item-relationships/item-type.model';
|
||||
import {getRemoteDataPayload, getSucceededRemoteData} from '../shared/operators';
|
||||
|
||||
/**
|
||||
* Service handling all ItemType requests
|
||||
@@ -51,6 +52,20 @@ export class EntityTypeService extends DataService<ItemType> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given entity type is the left type of a given relationship type, as an observable boolean
|
||||
* @param relationshipType the relationship type for which to check whether the given entity type is the left type
|
||||
* @param entityType the entity type for which to check whether it is the left type of the given relationship type
|
||||
*/
|
||||
isLeftType(relationshipType: RelationshipType, itemType: ItemType): Observable<boolean> {
|
||||
|
||||
return relationshipType.leftType.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((leftType) => leftType.uuid === itemType.uuid),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allowed relationship types for an entity type
|
||||
* @param entityTypeId
|
||||
|
@@ -5,6 +5,7 @@ export enum FeatureID {
|
||||
LoginOnBehalfOf = 'loginOnBehalfOf',
|
||||
AdministratorOf = 'administratorOf',
|
||||
CanDelete = 'canDelete',
|
||||
CanEditMetadata = 'canEditMetadata',
|
||||
WithdrawItem = 'withdrawItem',
|
||||
ReinstateItem = 'reinstateItem',
|
||||
EPersonRegistration = 'epersonRegistration',
|
||||
|
@@ -13,9 +13,11 @@ import {
|
||||
SelectVirtualMetadataAction,
|
||||
} from './object-updates.actions';
|
||||
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
||||
import {Relationship} from '../../shared/item-relationships/relationship.model';
|
||||
import { Relationship} from '../../shared/item-relationships/relationship.model';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
|
||||
import { Item} from '../../shared/item.model';
|
||||
import { RelationshipType} from '../../shared/item-relationships/relationship-type.model';
|
||||
|
||||
/**
|
||||
* Path where discarded objects are saved
|
||||
@@ -74,11 +76,18 @@ export interface VirtualMetadataSource {
|
||||
[uuid: string]: boolean,
|
||||
}
|
||||
|
||||
export interface RelationshipIdentifiable extends Identifiable {
|
||||
nameVariant?: string,
|
||||
relatedItem: Item;
|
||||
relationship: Relationship;
|
||||
type: RelationshipType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fieldupdate interface which represents a relationship selected to be deleted,
|
||||
* along with a selection of the virtual metadata to keep
|
||||
*/
|
||||
export interface DeleteRelationship extends Relationship {
|
||||
export interface DeleteRelationship extends RelationshipIdentifiable {
|
||||
keepLeftVirtualMetadata: boolean,
|
||||
keepRightVirtualMetadata: boolean,
|
||||
}
|
||||
@@ -189,7 +198,7 @@ function addFieldUpdate(state: any, action: AddFieldUpdateAction) {
|
||||
const url: string = action.payload.url;
|
||||
const field: Identifiable = action.payload.field;
|
||||
const changeType: FieldChangeType = action.payload.changeType;
|
||||
const pageState: ObjectUpdatesEntry = state[url] || {};
|
||||
const pageState: ObjectUpdatesEntry = state[url] || {fieldUpdates: {}};
|
||||
|
||||
let states = pageState.fieldStates;
|
||||
if (changeType === FieldChangeType.ADD) {
|
||||
|
@@ -24,7 +24,7 @@ import {
|
||||
SetValidFieldUpdateAction
|
||||
} from './object-updates.actions';
|
||||
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { INotification } from '../../../shared/notifications/models/notification.model';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
|
||||
@@ -129,7 +129,7 @@ export class ObjectUpdatesService {
|
||||
*/
|
||||
getFieldUpdatesExclusive(url: string, initialFields: Identifiable[]): Observable<FieldUpdates> {
|
||||
const objectUpdates = this.getObjectEntry(url);
|
||||
return objectUpdates.pipe(isNotEmptyOperator(), map((objectEntry) => {
|
||||
return objectUpdates.pipe(map((objectEntry) => {
|
||||
const fieldUpdates: FieldUpdates = {};
|
||||
for (const object of initialFields) {
|
||||
let fieldUpdate = objectEntry.fieldUpdates[object.uuid];
|
||||
|
@@ -102,8 +102,8 @@ export class RelationshipService extends DataService<Relationship> {
|
||||
),
|
||||
configureRequest(this.requestService),
|
||||
switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
|
||||
getResponseFromEntry(),
|
||||
tap(() => this.refreshRelationshipItemsInCacheByRelationship(id)),
|
||||
getResponseFromEntry(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,9 +129,9 @@ export class RelationshipService extends DataService<Relationship> {
|
||||
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, `${item1.self} \n ${item2.self}`, options)),
|
||||
configureRequest(this.requestService),
|
||||
switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
|
||||
getResponseFromEntry(),
|
||||
tap(() => this.refreshRelationshipItemsInCache(item1)),
|
||||
tap(() => this.refreshRelationshipItemsInCache(item2))
|
||||
tap(() => this.refreshRelationshipItemsInCache(item2)),
|
||||
getResponseFromEntry(),
|
||||
) as Observable<RestResponse>;
|
||||
}
|
||||
|
||||
@@ -400,6 +400,20 @@ export class RelationshipService extends DataService<Relationship> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given item is the left item of a given relationship, as an observable boolean
|
||||
* @param relationship the relationship for which to check whether the given item is the left item
|
||||
* @param item the item for which to check whether it is the left item of the given relationship
|
||||
*/
|
||||
public isLeftItem(relationship: Relationship, item: Item): Observable<boolean> {
|
||||
return relationship.leftItem.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((leftItem: Item) => hasValue(leftItem) && isNotEmpty(leftItem.uuid)),
|
||||
map((leftItem) => leftItem.uuid === item.uuid)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to update the the right or left place of a relationship
|
||||
* The useLeftItem field in the reorderable relationship determines which place should be updated
|
||||
|
@@ -55,4 +55,8 @@ export class RemoteData<T> {
|
||||
return this.state === RemoteDataState.Success;
|
||||
}
|
||||
|
||||
get hasNoContent(): boolean {
|
||||
return this.statusCode === 204;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -303,7 +303,7 @@ describe('EPersonDataService', () => {
|
||||
it('should sent a patch request with an uuid, token and new password to the epersons endpoint', () => {
|
||||
service.patchPasswordWithToken('test-uuid', 'test-token','test-password');
|
||||
|
||||
const operation = Object.assign({ op: 'replace', path: '/password', value: 'test-password' });
|
||||
const operation = Object.assign({ op: 'add', path: '/password', value: 'test-password' });
|
||||
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/test-uuid?token=test-token', [operation]);
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
|
@@ -280,7 +280,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
||||
patchPasswordWithToken(uuid: string, token: string, password: string): Observable<RestResponse> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
|
||||
const operation = Object.assign({ op: 'replace', path: '/password', value: password });
|
||||
const operation = Object.assign({ op: 'add', path: '/password', value: password });
|
||||
|
||||
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
||||
map((endpoint: string) => this.getIDHref(endpoint, uuid)),
|
||||
|
24
src/app/core/shared/collection.model.spec.ts
Normal file
24
src/app/core/shared/collection.model.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Collection} from './collection.model';
|
||||
|
||||
describe('Collection', () => {
|
||||
|
||||
describe('Collection handle value', () => {
|
||||
|
||||
let metadataValue;
|
||||
|
||||
beforeEach(() => {
|
||||
metadataValue = {'dc.identifier.uri': [ { value: '123456789/1'}]};
|
||||
})
|
||||
|
||||
it('should return the handle value from metadata', () => {
|
||||
const community = Object.assign(new Collection(), { metadata: metadataValue });
|
||||
expect(community.handle).toEqual('123456789/1');
|
||||
});
|
||||
|
||||
it('should return undefined if the handle value from metadata is not present', () => {
|
||||
const community = Object.assign(new Collection(), { });
|
||||
expect(community.handle).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
import { deserialize, inheritSerialization } from 'cerialize';
|
||||
import { Observable } from 'rxjs';
|
||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
@@ -21,12 +21,6 @@ import { ChildHALResource } from './child-hal-resource.model';
|
||||
export class Collection extends DSpaceObject implements ChildHALResource {
|
||||
static type = COLLECTION;
|
||||
|
||||
/**
|
||||
* A string representing the unique handle of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
handle: string;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Collection
|
||||
*/
|
||||
@@ -75,6 +69,13 @@ export class Collection extends DSpaceObject implements ChildHALResource {
|
||||
@link(COMMUNITY, false)
|
||||
parentCommunity?: Observable<RemoteData<Community>>;
|
||||
|
||||
/**
|
||||
* A string representing the unique handle of this Collection
|
||||
*/
|
||||
get handle(): string {
|
||||
return this.firstMetadataValue('dc.identifier.uri');
|
||||
}
|
||||
|
||||
/**
|
||||
* The introductory text of this Collection
|
||||
* Corresponds to the metadata field dc.description
|
||||
|
24
src/app/core/shared/community.model.spec.ts
Normal file
24
src/app/core/shared/community.model.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Community} from './community.model';
|
||||
|
||||
describe('Community', () => {
|
||||
|
||||
describe('Community handle value', () => {
|
||||
|
||||
let metadataValue;
|
||||
|
||||
beforeEach(() => {
|
||||
metadataValue = {'dc.identifier.uri': [ { value: '123456789/1'}]};
|
||||
})
|
||||
|
||||
it('should return the handle value from metadata', () => {
|
||||
const community = Object.assign(new Community(), { metadata: metadataValue });
|
||||
expect(community.handle).toEqual('123456789/1');
|
||||
});
|
||||
|
||||
it('should return undefined if the handle value from metadata is not present', () => {
|
||||
const community = Object.assign(new Community(), { });
|
||||
expect(community.handle).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
||||
import { deserialize, inheritSerialization } from 'cerialize';
|
||||
import { Observable } from 'rxjs';
|
||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
@@ -17,12 +17,6 @@ import { ChildHALResource } from './child-hal-resource.model';
|
||||
export class Community extends DSpaceObject implements ChildHALResource {
|
||||
static type = COMMUNITY;
|
||||
|
||||
/**
|
||||
* A string representing the unique handle of this Community
|
||||
*/
|
||||
@autoserialize
|
||||
handle: string;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Community
|
||||
*/
|
||||
@@ -64,6 +58,13 @@ export class Community extends DSpaceObject implements ChildHALResource {
|
||||
@link(COMMUNITY, false)
|
||||
parentCommunity?: Observable<RemoteData<Community>>;
|
||||
|
||||
/**
|
||||
* A string representing the unique handle of this Community
|
||||
*/
|
||||
get handle(): string {
|
||||
return this.firstMetadataValue('dc.identifier.uri');
|
||||
}
|
||||
|
||||
/**
|
||||
* The introductory text of this Community
|
||||
* Corresponds to the metadata field dc.description
|
||||
|
@@ -13,4 +13,6 @@ export enum Context {
|
||||
EntitySearchModal = 'EntitySearchModal',
|
||||
AdminSearch = 'adminSearch',
|
||||
AdminWorkflowSearch = 'adminWorkflowSearch',
|
||||
SideBarSearchModal = 'sideBarSearchModal',
|
||||
SideBarSearchModalCurrent = 'sideBarSearchModalCurrent',
|
||||
}
|
||||
|
@@ -76,6 +76,10 @@ export const getSucceededRemoteWithNotEmptyData = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
||||
|
||||
export const getSucceededOrNoContentResponse = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded || rd.hasNoContent));
|
||||
|
||||
/**
|
||||
* Get the first successful remotely retrieved object
|
||||
*
|
||||
|
9
src/app/core/shared/process-output.resource-type.ts
Normal file
9
src/app/core/shared/process-output.resource-type.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for ProcessOutput
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const PROCESS_OUTPUT_TYPE = new ResourceType('processOutput');
|
@@ -0,0 +1,42 @@
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||
import { JournalIssueSidebarSearchListElementComponent } from './journal-issue-sidebar-search-list-element.component';
|
||||
|
||||
const object = Object.assign(new ItemSearchResult(), {
|
||||
indexableObject: Object.assign(new Item(), {
|
||||
id: 'test-item',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'title'
|
||||
}
|
||||
],
|
||||
'publicationvolume.volumeNumber': [
|
||||
{
|
||||
value: '5'
|
||||
}
|
||||
],
|
||||
'publicationissue.issueNumber': [
|
||||
{
|
||||
value: '7'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
const parent = Object.assign(new Collection(), {
|
||||
id: 'test-collection',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'parent title'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
describe('JournalIssueSidebarSearchListElementComponent',
|
||||
createSidebarSearchListElementTests(JournalIssueSidebarSearchListElementComponent, object, parent, 'parent title', 'title', '5 - 7')
|
||||
);
|
@@ -0,0 +1,39 @@
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||
|
||||
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||
@Component({
|
||||
selector: 'ds-journal-issue-sidebar-search-list-element',
|
||||
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalIssue" within the context of
|
||||
* a sidebar search modal
|
||||
*/
|
||||
export class JournalIssueSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||
/**
|
||||
* Get the description of the Journal Issue by returning its volume number(s) and/or issue number(s)
|
||||
*/
|
||||
getDescription(): string {
|
||||
const volumeNumbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
|
||||
const issueNumbers = this.allMetadataValues(['publicationissue.issueNumber']);
|
||||
let description = '';
|
||||
if (isNotEmpty(volumeNumbers)) {
|
||||
description += volumeNumbers.join(', ');
|
||||
}
|
||||
if (isNotEmpty(description) && isNotEmpty(issueNumbers)) {
|
||||
description += ' - ';
|
||||
}
|
||||
if (isNotEmpty(issueNumbers)) {
|
||||
description += issueNumbers.join(', ');
|
||||
}
|
||||
return this.undefinedIfEmpty(description);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||
import { JournalVolumeSidebarSearchListElementComponent } from './journal-volume-sidebar-search-list-element.component';
|
||||
|
||||
const object = Object.assign(new ItemSearchResult(), {
|
||||
indexableObject: Object.assign(new Item(), {
|
||||
id: 'test-item',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'title'
|
||||
}
|
||||
],
|
||||
'journal.title': [
|
||||
{
|
||||
value: 'journal title'
|
||||
}
|
||||
],
|
||||
'publicationvolume.volumeNumber': [
|
||||
{
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
const parent = Object.assign(new Collection(), {
|
||||
id: 'test-collection',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'parent title'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
describe('JournalVolumeSidebarSearchListElementComponent',
|
||||
createSidebarSearchListElementTests(JournalVolumeSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'journal title (1) (2)')
|
||||
);
|
@@ -0,0 +1,39 @@
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||
|
||||
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||
@Component({
|
||||
selector: 'ds-journal-volume-sidebar-search-list-element',
|
||||
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* Component displaying a list element for a {@link ItemSearchResult} of type "JournalVolume" within the context of
|
||||
* a sidebar search modal
|
||||
*/
|
||||
export class JournalVolumeSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||
/**
|
||||
* Get the description of the Journal Volume by returning the journal title and volume number(s) (between parentheses)
|
||||
*/
|
||||
getDescription(): string {
|
||||
const titles = this.allMetadataValues(['journal.title']);
|
||||
const numbers = this.allMetadataValues(['publicationvolume.volumeNumber']);
|
||||
let description = '';
|
||||
if (isNotEmpty(titles)) {
|
||||
description += titles.join(', ');
|
||||
}
|
||||
if (isNotEmpty(numbers)) {
|
||||
if (isNotEmpty(description)) {
|
||||
description += ' ';
|
||||
}
|
||||
description += numbers.map((n) => `(${n})`).join(' ');
|
||||
}
|
||||
return this.undefinedIfEmpty(description);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||
import { JournalSidebarSearchListElementComponent } from './journal-sidebar-search-list-element.component';
|
||||
|
||||
const object = Object.assign(new ItemSearchResult(), {
|
||||
indexableObject: Object.assign(new Item(), {
|
||||
id: 'test-item',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'title'
|
||||
}
|
||||
],
|
||||
'creativeworkseries.issn': [
|
||||
{
|
||||
value: '1234'
|
||||
},
|
||||
{
|
||||
value: '5678'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
const parent = Object.assign(new Collection(), {
|
||||
id: 'test-collection',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'parent title'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
describe('JournalSidebarSearchListElementComponent',
|
||||
createSidebarSearchListElementTests(JournalSidebarSearchListElementComponent, object, parent, 'parent title', 'title', '1234, 5678')
|
||||
);
|
@@ -0,0 +1,32 @@
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||
|
||||
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||
@Component({
|
||||
selector: 'ds-journal-sidebar-search-list-element',
|
||||
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* Component displaying a list element for a {@link ItemSearchResult} of type "Journal" within the context of
|
||||
* a sidebar search modal
|
||||
*/
|
||||
export class JournalSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||
/**
|
||||
* Get the description of the Journal by returning its ISSN(s)
|
||||
*/
|
||||
getDescription(): string {
|
||||
const issns = this.allMetadataValues(['creativeworkseries.issn']);
|
||||
let description = '';
|
||||
if (isNotEmpty(issns)) {
|
||||
description += issns.join(', ');
|
||||
}
|
||||
return this.undefinedIfEmpty(description);
|
||||
}
|
||||
}
|
@@ -1,6 +1,11 @@
|
||||
<h2 class="item-page-title-field">
|
||||
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="d-flex flex-row">
|
||||
<h2 class="item-page-title-field mr-auto">
|
||||
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journalissue.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<h2 class="item-page-title-field">
|
||||
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="d-flex flex-row">
|
||||
<h2 class="item-page-title-field mr-auto">
|
||||
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journalvolume.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
|
@@ -1,6 +1,11 @@
|
||||
<h2 class="item-page-title-field">
|
||||
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="d-flex flex-row">
|
||||
<h2 class="item-page-title-field mr-auto">
|
||||
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'journal.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
|
@@ -18,6 +18,9 @@ import { JournalIssueSearchResultListElementComponent } from './item-list-elemen
|
||||
import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component';
|
||||
import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component';
|
||||
import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component';
|
||||
import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component';
|
||||
import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component';
|
||||
import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
JournalComponent,
|
||||
@@ -34,7 +37,10 @@ const ENTRY_COMPONENTS = [
|
||||
JournalVolumeSearchResultListElementComponent,
|
||||
JournalIssueSearchResultGridElementComponent,
|
||||
JournalVolumeSearchResultGridElementComponent,
|
||||
JournalSearchResultGridElementComponent
|
||||
JournalSearchResultGridElementComponent,
|
||||
JournalVolumeSidebarSearchListElementComponent,
|
||||
JournalIssueSidebarSearchListElementComponent,
|
||||
JournalSidebarSearchListElementComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1 +1,6 @@
|
||||
<ds-person-search-result-list-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType"></ds-person-search-result-list-element>
|
||||
<ds-person-search-result-list-element [object]="{ indexableObject: object, hitHighlights: {} }"
|
||||
[linkType]="linkType"
|
||||
[showLabel]="showLabel"
|
||||
[value]="value"
|
||||
>
|
||||
</ds-person-search-result-list-element>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<ds-truncatable [id]="dso.id">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></a>
|
||||
[innerHTML]="name"></a>
|
||||
<span *ngIf="linkType == linkTypes.None"
|
||||
class="lead"
|
||||
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></span>
|
||||
|
@@ -15,4 +15,10 @@ import { Item } from '../../../../../core/shared/item.model';
|
||||
* The component for displaying a list element for an item search result of the type Person
|
||||
*/
|
||||
export class PersonSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
|
||||
|
||||
get name() {
|
||||
return this.value ?
|
||||
this.value :
|
||||
this.firstMetadataValue('person.familyName') + ', ' + this.firstMetadataValue('person.givenName');
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||
import { OrgUnitSidebarSearchListElementComponent } from './org-unit-sidebar-search-list-element.component';
|
||||
|
||||
const object = Object.assign(new ItemSearchResult(), {
|
||||
indexableObject: Object.assign(new Item(), {
|
||||
id: 'test-item',
|
||||
metadata: {
|
||||
'organization.legalName': [
|
||||
{
|
||||
value: 'title'
|
||||
}
|
||||
],
|
||||
'dc.description': [
|
||||
{
|
||||
value: 'description'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
const parent = Object.assign(new Collection(), {
|
||||
id: 'test-collection',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'parent title'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
describe('OrgUnitSidebarSearchListElementComponent',
|
||||
createSidebarSearchListElementTests(OrgUnitSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
|
||||
);
|
@@ -0,0 +1,33 @@
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
|
||||
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||
@Component({
|
||||
selector: 'ds-org-unit-sidebar-search-list-element',
|
||||
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* Component displaying a list element for a {@link ItemSearchResult} of type "OrgUnit" within the context of
|
||||
* a sidebar search modal
|
||||
*/
|
||||
export class OrgUnitSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||
/**
|
||||
* Get the title of the Org Unit by returning its legal name
|
||||
*/
|
||||
getTitle(): string {
|
||||
return this.firstMetadataValue('organization.legalName');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the Org Unit by returning its dc.description
|
||||
*/
|
||||
getDescription(): string {
|
||||
return this.firstMetadataValue('dc.description');
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { createSidebarSearchListElementTests } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec';
|
||||
import { PersonSidebarSearchListElementComponent } from './person-sidebar-search-list-element.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
const object = Object.assign(new ItemSearchResult(), {
|
||||
indexableObject: Object.assign(new Item(), {
|
||||
id: 'test-item',
|
||||
metadata: {
|
||||
'person.familyName': [
|
||||
{
|
||||
value: 'family name'
|
||||
}
|
||||
],
|
||||
'person.givenName': [
|
||||
{
|
||||
value: 'given name'
|
||||
}
|
||||
],
|
||||
'person.jobTitle': [
|
||||
{
|
||||
value: 'job title'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
});
|
||||
const parent = Object.assign(new Collection(), {
|
||||
id: 'test-collection',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
value: 'parent title'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
describe('PersonSidebarSearchListElementComponent',
|
||||
createSidebarSearchListElementTests(PersonSidebarSearchListElementComponent, object, parent, 'parent title', 'family name, given name', 'job title', [
|
||||
{ provide: TranslateService, useValue: jasmine.createSpyObj('translate', { instant: '' }) }
|
||||
])
|
||||
);
|
@@ -0,0 +1,60 @@
|
||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../../../../shared/empty.util';
|
||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||
@Component({
|
||||
selector: 'ds-person-sidebar-search-list-element',
|
||||
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* Component displaying a list element for a {@link ItemSearchResult} of type "Person" within the context of
|
||||
* a sidebar search modal
|
||||
*/
|
||||
export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||
constructor(protected truncatableService: TruncatableService,
|
||||
protected linkService: LinkService,
|
||||
protected translateService: TranslateService) {
|
||||
super(truncatableService, linkService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of the Person by returning a combination of its family name and given name (or "No name found")
|
||||
*/
|
||||
getTitle(): string {
|
||||
const familyName = this.firstMetadataValue('person.familyName');
|
||||
const givenName = this.firstMetadataValue('person.givenName');
|
||||
let title = '';
|
||||
if (isNotEmpty(familyName)) {
|
||||
title = familyName;
|
||||
}
|
||||
if (isNotEmpty(title)) {
|
||||
title += ', ';
|
||||
}
|
||||
if (isNotEmpty(givenName)) {
|
||||
title += givenName;
|
||||
}
|
||||
return this.defaultIfEmpty(title, this.translateService.instant('person.listelement.no-title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the Person by returning its job title(s)
|
||||
*/
|
||||
getDescription(): string {
|
||||
const titles = this.allMetadataValues(['person.jobTitle']);
|
||||
let description = '';
|
||||
if (isNotEmpty(titles)) {
|
||||
description += titles.join(', ');
|
||||
}
|
||||
return this.undefinedIfEmpty(description);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user