1
0

Merge branch 'main' into w2p-75058_Private-Withdrawn-badges-for-items-everywhere

Conflicts:
	src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts
	src/app/shared/shared.module.ts
This commit is contained in:
Kristof De Langhe
2020-12-14 11:44:57 +01:00
98 changed files with 997 additions and 230 deletions

View File

@@ -5,7 +5,7 @@
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
- docker-compose-rest.yml
- Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes
- docker-compose-travis.yml
- docker-compose-ci.yml
- Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup.
- cli.yml
- Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.

View File

@@ -1,3 +1,17 @@
#
# The contents of this file are subject to the license and copyright
# detailed in the LICENSE and NOTICE files at the root of the source
# tree and available online at
#
# http://www.dspace.org/license/
#
#
# This is a copy of the cli.ingest.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
networks:
@@ -8,7 +22,7 @@ services:
networks:
dspacenet: {}
environment:
- LOADASSETS=https://www.dropbox.com/s/zv7lj8j2lp3egjs/assetstore.tar.gz?dl=1
- LOADASSETS=https://www.dropbox.com/s/v3ahfcuatklbmi0/assetstore-2019-11-28.tar.gz?dl=1
entrypoint:
- /bin/bash
- '-c'
@@ -21,3 +35,5 @@ services:
fi
/dspace/bin/dspace index-discovery
/dspace/bin/dspace oai import
/dspace/bin/dspace oai clean-cache

View File

@@ -6,6 +6,12 @@
# http://www.dspace.org/license/
#
#
# This is a copy of the cli.ingest.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
services:

View File

@@ -1,3 +1,17 @@
#
# The contents of this file are subject to the license and copyright
# detailed in the LICENSE and NOTICE files at the root of the source
# tree and available online at
#
# http://www.dspace.org/license/
#
#
# This is a copy of the docker-compose-cli.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/docker-compose-cli.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
services:

View File

@@ -6,11 +6,17 @@
# http://www.dspace.org/license/
#
#
# This is a copy of the db.entities.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
#
# # Therefore, it should be kept in sync with that file
version: "3.7"
services:
dspacedb:
image: dspace/dspace-postgres-pgcrypto:loadsql
environment:
# Double underbars in env names will be replaced with periods for apache commons
- LOADSQL=https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
- LOADSQL=https://www.dropbox.com/s/4ap1y6deseoc8ws/dspace7-entities-2019-11-28.sql?dl=1

View File

@@ -1,4 +1,13 @@
# Docker Compose for running the DSpace backend for e2e testing in CI
#
# The contents of this file are subject to the license and copyright
# detailed in the LICENSE and NOTICE files at the root of the source
# tree and available online at
#
# http://www.dspace.org/license/
#
# Docker Compose for running the DSpace backend for e2e testing in a CI environment
# This is used by our GitHub CI at .github/workflows/build.yml
networks:
dspacenet:
services:
@@ -20,7 +29,9 @@ services:
dspacedb:
container_name: dspacedb
environment:
LOADSQL: https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
# This LOADSQL should be kept in sync with the LOADSQL in
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
LOADSQL: https://www.dropbox.com/s/4ap1y6deseoc8ws/dspace7-entities-2019-11-28.sql?dl=1
PGDATA: /pgdata
image: dspace/dspace-postgres-pgcrypto:loadsql
networks:

View File

@@ -1,11 +1,24 @@
#
# The contents of this file are subject to the license and copyright
# detailed in the LICENSE and NOTICE files at the root of the source
# tree and available online at
#
# http://www.dspace.org/license/
#
# Docker Compose for running the DSpace backend for testing/development
# This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
version: '3.7'
networks:
dspacenet:
services:
dspace:
container_name: dspace
image: dspace/dspace:dspace-7_x-test
depends_on:
- dspacedb
image: dspace/dspace:dspace-7_x-test
networks:
dspacenet:
ports:
@@ -16,20 +29,27 @@ services:
volumes:
- assetstore:/dspace/assetstore
- "./local.cfg:/dspace/config/local.cfg"
# Ensure that the database is ready before starting tomcat
# Ensure that the database is ready BEFORE starting tomcat
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
# 2. Then, run database migration to init database tables
# 3. Finally, start Tomcat
entrypoint:
- /bin/bash
- '-c'
- |
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
/dspace/bin/dspace database migrate
catalina.sh run
dspacedb:
container_name: dspacedb
image: dspace/dspace-postgres-pgcrypto
environment:
PGDATA: /pgdata
image: dspace/dspace-postgres-pgcrypto
networks:
dspacenet:
ports:
- published: 5432
target: 5432
stdin_open: true
tty: true
volumes:
@@ -49,7 +69,6 @@ services:
- solr_oai:/opt/solr/server/solr/oai/data
- solr_search:/opt/solr/server/solr/search/data
- solr_statistics:/opt/solr/server/solr/statistics/data
version: '3.7'
volumes:
assetstore:
pgdata:

View File

@@ -1,3 +1,14 @@
#
# The contents of this file are subject to the license and copyright
# detailed in the LICENSE and NOTICE files at the root of the source
# tree and available online at
#
# http://www.dspace.org/license/
#
# Docker Compose for running the DSpace Angular UI for testing/development
# Requires also running a REST API backend (either locally or remotely),
# for example via 'docker-compose-rest.yml'
version: '3.7'
networks:
dspacenet:

View File

@@ -12,7 +12,7 @@ import { WorkflowItem } from '../../../../../core/submission/models/workflowitem
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
import { Item } from '../../../../../core/shared/item.model';
import { PublicationGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
@@ -43,7 +43,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
init();
TestBed.configureTestingModule(
{
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, PublicationGridElementComponent, ListableObjectDirective],
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
@@ -60,7 +60,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
})
.overrideComponent(WorkflowItemSearchResultAdminWorkflowGridElementComponent, {
set: {
entryComponents: [PublicationGridElementComponent]
entryComponents: [ItemGridElementComponent]
}
})
.compileComponents();

View File

@@ -0,0 +1,3 @@
<div class="container">
<ds-resource-policies [resourceType]="'collection'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
</div>

View File

@@ -0,0 +1,73 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { cold } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { CollectionAuthorizationsComponent } from './collection-authorizations.component';
import { Collection } from '../../../core/shared/collection.model';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
describe('CollectionAuthorizationsComponent', () => {
let comp: CollectionAuthorizationsComponent<DSpaceObject>;
let fixture: ComponentFixture<CollectionAuthorizationsComponent<any>>;
const collection = Object.assign(new Collection(), {
uuid: 'collection',
id: 'collection',
_links: {
self: { href: 'collection-selflink' }
}
});
const collectionRD = createSuccessfulRemoteDataObject(collection);
const routeStub = {
parent: {
parent: {
data: observableOf({
dso: collectionRD
})
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule
],
declarations: [CollectionAuthorizationsComponent],
providers: [
{ provide: ActivatedRoute, useValue: routeStub },
ChangeDetectorRef,
CollectionAuthorizationsComponent,
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionAuthorizationsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
comp = null;
fixture.destroy();
});
it('should create', () => {
expect(comp).toBeTruthy();
});
it('should init dso remote data properly', (done) => {
const expected = cold('(a|)', { a: collectionRD });
expect(comp.dsoRD$).toBeObservable(expected);
done();
});
});

View File

@@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
@Component({
selector: 'ds-collection-authorizations',
templateUrl: './collection-authorizations.component.html',
})
/**
* Component that handles the Collection Authorizations
*/
export class CollectionAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* The initial DSO object
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
/**
* Initialize instance variables
*
* @param {ActivatedRoute} route
*/
constructor(
private route: ActivatedRoute
) {
}
/**
* Initialize the component, setting up the collection
*/
ngOnInit(): void {
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
}
}

View File

@@ -8,6 +8,7 @@ import { CollectionPageModule } from '../collection-page.module';
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
/**
* Module that contains all components related to the Edit Collection page administrator functionality
@@ -24,7 +25,8 @@ import { CollectionSourceComponent } from './collection-source/collection-source
CollectionMetadataComponent,
CollectionRolesComponent,
CollectionCurateComponent,
CollectionSourceComponent
CollectionSourceComponent,
CollectionAuthorizationsComponent
]
})
export class EditCollectionPageModule {

View File

@@ -5,7 +5,12 @@ import { CollectionMetadataComponent } from './collection-metadata/collection-me
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
/**
* Routing module that handles the routing for the Edit Collection page administrator functionality
@@ -49,10 +54,46 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
path: 'curate',
component: CollectionCurateComponent,
data: { title: 'collection.edit.tabs.curate.title', showBreadcrumbs: true }
},
/* {
path: 'authorizations',
component: CollectionAuthorizationsComponent,
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
},*/
{
path: 'authorizations',
data: { showBreadcrumbs: true },
children: [
{
path: 'create',
resolve: {
resourcePolicyTarget: ResourcePolicyTargetResolver
},
component: ResourcePolicyCreateComponent,
data: { title: 'resource-policies.create.page.title' }
},
{
path: 'edit',
resolve: {
resourcePolicy: ResourcePolicyResolver
},
component: ResourcePolicyEditComponent,
data: { title: 'resource-policies.edit.page.title' }
},
{
path: '',
component: CollectionAuthorizationsComponent,
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
}
]
}
]
}
])
],
providers: [
ResourcePolicyResolver,
ResourcePolicyTargetResolver
]
})
export class EditCollectionPageRoutingModule {

View File

@@ -0,0 +1,3 @@
<div class="container">
<ds-resource-policies [resourceType]="'community'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
</div>

View File

@@ -0,0 +1,73 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { cold } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
import { CommunityAuthorizationsComponent } from './community-authorizations.component';
import { Collection } from '../../../core/shared/collection.model';
describe('CommunityAuthorizationsComponent', () => {
let comp: CommunityAuthorizationsComponent<DSpaceObject>;
let fixture: ComponentFixture<CommunityAuthorizationsComponent<any>>;
const community = Object.assign(new Collection(), {
uuid: 'community',
id: 'community',
_links: {
self: { href: 'community-selflink' }
}
});
const communityRD = createSuccessfulRemoteDataObject(community);
const routeStub = {
parent: {
parent: {
data: observableOf({
dso: communityRD
})
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule
],
declarations: [CommunityAuthorizationsComponent],
providers: [
{ provide: ActivatedRoute, useValue: routeStub },
ChangeDetectorRef,
CommunityAuthorizationsComponent,
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CommunityAuthorizationsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
comp = null;
fixture.destroy();
});
it('should create', () => {
expect(comp).toBeTruthy();
});
it('should init dso remote data properly', (done) => {
const expected = cold('(a|)', { a: communityRD });
expect(comp.dsoRD$).toBeObservable(expected);
done();
});
});

View File

@@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { RemoteData } from 'src/app/core/data/remote-data';
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
@Component({
selector: 'ds-community-authorizations',
templateUrl: './community-authorizations.component.html',
})
/**
* Component that handles the community Authorizations
*/
export class CommunityAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* The initial DSO object
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
/**
* Initialize instance variables
*
* @param {ActivatedRoute} route
*/
constructor(
private route: ActivatedRoute
) {
}
/**
* Initialize the component, setting up the community
*/
ngOnInit(): void {
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
}
}

View File

@@ -7,6 +7,7 @@ import { EditCommunityPageComponent } from './edit-community-page.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
import { CommunityRolesComponent } from './community-roles/community-roles.component';
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
/**
* Module that contains all components related to the Edit Community page administrator functionality
@@ -22,7 +23,8 @@ import { CommunityRolesComponent } from './community-roles/community-roles.compo
EditCommunityPageComponent,
CommunityCurateComponent,
CommunityMetadataComponent,
CommunityRolesComponent
CommunityRolesComponent,
CommunityAuthorizationsComponent
]
})
export class EditCommunityPageModule {

View File

@@ -1,4 +1,3 @@
import { CommunityPageResolver } from '../community-page.resolver';
import { EditCommunityPageComponent } from './edit-community-page.component';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
@@ -6,6 +5,11 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
import { CommunityRolesComponent } from './community-roles/community-roles.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
/**
* Routing module that handles the routing for the Edit Community page administrator functionality
@@ -44,11 +48,47 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
path: 'curate',
component: CommunityCurateComponent,
data: { title: 'community.edit.tabs.curate.title', showBreadcrumbs: true }
},
/*{
path: 'authorizations',
component: CommunityAuthorizationsComponent,
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true }
},*/
{
path: 'authorizations',
data: { showBreadcrumbs: true },
children: [
{
path: 'create',
resolve: {
resourcePolicyTarget: ResourcePolicyTargetResolver
},
component: ResourcePolicyCreateComponent,
data: { title: 'resource-policies.create.page.title' }
},
{
path: 'edit',
resolve: {
resourcePolicy: ResourcePolicyResolver
},
component: ResourcePolicyEditComponent,
data: { title: 'resource-policies.edit.page.title' }
},
{
path: '',
component: CommunityAuthorizationsComponent,
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true, hideReturnButton: true }
}
]
}
]
}
])
],
providers: [
ResourcePolicyResolver,
ResourcePolicyTargetResolver
]
})
export class EditCommunityPageRoutingModule {

View File

@@ -30,8 +30,6 @@ import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/it
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
/**
@@ -71,9 +69,7 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
ItemMoveComponent,
ItemEditBitstreamDragHandleComponent,
VirtualMetadataComponent,
ItemAuthorizationsComponent,
ResourcePolicyEditComponent,
ResourcePolicyCreateComponent,
ItemAuthorizationsComponent
],
providers: [
BundleDataService,

View File

@@ -30,6 +30,7 @@ import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.c
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
import { StatisticsModule } from '../statistics/statistics.module';
import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component';
@NgModule({
imports: [
@@ -54,6 +55,7 @@ import { AbstractIncrementalListComponent } from './simple/abstract-incremental-
CollectionsComponent,
FullFileSectionComponent,
PublicationComponent,
UntypedItemComponent,
RelatedItemsComponent,
ItemComponent,
GenericItemPageFieldComponent,
@@ -75,7 +77,8 @@ import { AbstractIncrementalListComponent } from './simple/abstract-incremental-
TabbedRelatedEntitiesSearchComponent
],
entryComponents: [
PublicationComponent
PublicationComponent,
UntypedItemComponent
]
})
export class ItemPageModule {

View File

@@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { ItemComponent } from '../shared/item.component';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
@@ -9,7 +8,6 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh
*/
@listableObjectComponent('Publication', ViewMode.StandalonePage)
@listableObjectComponent(Item, ViewMode.StandalonePage)
@Component({
selector: 'ds-publication',
styleUrls: ['./publication.component.scss'],

View File

@@ -0,0 +1,67 @@
<div class="d-flex flex-row">
<h2 class="item-page-title-field mr-auto">
<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]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper>
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
<ds-generic-item-page-field [item]="object"
[fields]="['journal.title']"
[label]="'item.page.journal-title'">
</ds-generic-item-page-field>
<ds-generic-item-page-field [item]="object"
[fields]="['journal.identifier.issn']"
[label]="'item.page.journal-issn'">
</ds-generic-item-page-field>
<ds-generic-item-page-field [item]="object"
[fields]="['journalvolume.identifier.name']"
[label]="'item.page.volume-title'">
</ds-generic-item-page-field>
<ds-generic-item-page-field [item]="object"
[fields]="['dc.publisher']"
[label]="'item.page.publisher'">
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-metadata-representation-list
[parentItem]="object"
[itemType]="'Person'"
[metadataField]="'dc.contributor.author'"
[label]="'relationships.isAuthorOf' | translate">
</ds-metadata-representation-list>
<ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
<ds-generic-item-page-field [item]="object"
[fields]="['dc.description']"
[label]="'item.page.description'">
</ds-generic-item-page-field>
<ds-generic-item-page-field [item]="object"
[fields]="['dc.subject']"
[separator]="','"
[label]="'item.page.subject'">
</ds-generic-item-page-field>
<ds-generic-item-page-field [item]="object"
[fields]="['dc.identifier.citation']"
[label]="'item.page.citation'">
</ds-generic-item-page-field>
<ds-item-page-uri-field [item]="object"
[fields]="['dc.identifier.uri']"
[label]="'item.page.uri'">
</ds-item-page-uri-field>
<ds-item-page-collections [item]="object"></ds-item-page-collections>
<div>
<a class="btn btn-outline-primary" [routerLink]="['/items/' + object.id + '/full']">
{{"item.page.link.full" | translate}}
</a>
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
@import '../../../../../styles/variables.scss';

View File

@@ -0,0 +1,112 @@
import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Store } from '@ngrx/store';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { CommunityDataService } from '../../../../core/data/community-data.service';
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RelationshipService } from '../../../../core/data/relationship.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
import { Item } from '../../../../core/shared/item.model';
import { MetadataMap } from '../../../../core/shared/metadata.models';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { UUIDService } from '../../../../core/shared/uuid.service';
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { createRelationshipsObservable } from '../shared/item.component.spec';
import { UntypedItemComponent } from './untyped-item.component';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: new MetadataMap(),
relationships: createRelationshipsObservable()
});
describe('UntypedItemComponent', () => {
let comp: UntypedItemComponent;
let fixture: ComponentFixture<UntypedItemComponent>;
beforeEach(async(() => {
const mockBitstreamDataService = {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
}
};
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})],
declarations: [UntypedItemComponent, GenericItemPageFieldComponent, TruncatePipe],
providers: [
{ provide: ItemDataService, useValue: {} },
{ provide: TruncatableService, useValue: {} },
{ provide: RelationshipService, useValue: {} },
{ provide: ObjectCacheService, useValue: {} },
{ provide: UUIDService, useValue: {} },
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: CommunityDataService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
{ provide: NotificationsService, useValue: {} },
{ provide: HttpClient, useValue: {} },
{ provide: DSOChangeAnalyzer, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(UntypedItemComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(UntypedItemComponent);
comp = fixture.componentInstance;
comp.object = mockItem;
fixture.detectChanges();
}));
it('should contain a component to display the date', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the author', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the abstract', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the uri', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the collections', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
});

View File

@@ -0,0 +1,20 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { ItemComponent } from '../shared/item.component';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
/**
* Component that represents a publication Item page
*/
@listableObjectComponent(Item, ViewMode.StandalonePage)
@Component({
selector: 'ds-untyped-item',
styleUrls: ['./untyped-item.component.scss'],
templateUrl: './untyped-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UntypedItemComponent extends ItemComponent {
}

View File

@@ -15,7 +15,7 @@ describe('RelatedEntitiesSearchComponent', () => {
});
const mockRelationType = 'publicationsOfAuthor';
const mockConfiguration = 'publication';
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
const mockFilter= `f.${mockRelationType}=${mockItem.id},equals`;
beforeEach(async(() => {
TestBed.configureTestingModule({

View File

@@ -33,6 +33,21 @@ export abstract class TypedObject {
type: ResourceType;
}
/**
* Get the string value for an object that may be a string or a ResourceType
*
* @param type the object to get the type value for
*/
export const getResourceTypeValueFor = (type: any): string => {
if (hasValue(type)) {
if (typeof type === 'string') {
return type;
} else if (typeof type.value === 'string') {
return type.value;
}
}
}
/* tslint:disable:max-classes-per-file */
/**
* An interface to represent objects that can be cached

View File

@@ -88,30 +88,10 @@ export class EntityTypeService extends DataService<ItemType> {
* @param label
*/
getEntityTypeByLabel(label: string): Observable<RemoteData<ItemType>> {
// TODO: Remove mock data once REST API supports this
/*
href$.pipe(take(1)).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
});
return this.rdbService.buildSingle<EntityType>(href$);
*/
// Mock:
const index = [
'Publication',
'Person',
'Project',
'OrgUnit',
'Journal',
'JournalVolume',
'JournalIssue',
'DataPackage',
'DataFile',
].indexOf(label);
return this.findById((index + 1) + '');
return this.halService.getEndpoint(this.linkPath).pipe(
take(1),
switchMap((endPoint: string) =>
this.findByHref(endPoint + '/label/' + label))
);
}
}

View File

@@ -1,7 +1,6 @@
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
import { Observable } from 'rxjs/internal/Observable';
import { isEmpty } from '../../shared/empty.util';
import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { link, typedObject } from '../cache/builders/build-decorators';
import { PaginatedList } from '../data/paginated-list';
@@ -105,9 +104,9 @@ export class Item extends DSpaceObject implements ChildHALResource {
* Method that returns as which type of object this object should be rendered
*/
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
let entityType = this.firstMetadataValue('relationship.type');
const entityType = this.firstMetadataValue('relationship.type');
if (isEmpty(entityType)) {
entityType = DEFAULT_ENTITY_TYPE;
return super.getRenderTypes();
}
return [entityType, ...super.getRenderTypes()];
}

View File

@@ -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 getSucceededRemoteWithNotEmptyDataOrFailed = () =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => (rd.hasSucceeded && isNotEmpty(rd.payload)) || rd.hasFailed));
export const getSucceededOrNoContentResponse = () =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded || rd.hasNoContent));

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>

View File

@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { JournalIssueSearchResultGridElementComponent } from './journal-issue-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>

View File

@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { JournalVolumeSearchResultGridElementComponent } from './journal-volume-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>

View File

@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { JournalSearchResultGridElementComponent } from './journal-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<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"

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<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"

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<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"

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('organization.legalName')"></h4>
</ds-truncatable-part>

View File

@@ -4,7 +4,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { OrgUnitSearchResultGridElementComponent } from './org-unit-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title"
[innerHTML]="firstMetadataValue('person.familyName') + ', ' + firstMetadataValue('person.givenName')"></h4>

View File

@@ -3,8 +3,8 @@ import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { PersonSearchResultGridElementComponent } from './person-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -19,7 +19,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>

View File

@@ -4,7 +4,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
import { ProjectSearchResultGridElementComponent } from './project-search-result-grid-element.component';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<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"

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<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"

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="['/items/' + dso.id]" class="lead"
[innerHTML]="firstMetadataValue('dc.title')"></a>

View File

@@ -6,7 +6,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor'
import { Context } from '../../../../core/shared/context.model';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import * as listableObjectDecorators from './listable-object.decorator';
import { PublicationListElementComponent } from '../../../object-list/item-list-element/item-types/publication/publication-list-element.component';
import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
import { ListableObjectDirective } from './listable-object.directive';
import { spyOnExported } from '../../../testing/utils.test';
import { TranslateModule } from '@ngx-translate/core';
@@ -30,13 +30,13 @@ describe('ListableObjectComponentLoaderComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ListableObjectComponentLoaderComponent, PublicationListElementComponent, ListableObjectDirective],
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective],
schemas: [NO_ERRORS_SCHEMA],
providers: [ComponentFactoryResolver]
}).overrideComponent(ListableObjectComponentLoaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
entryComponents: [PublicationListElementComponent]
entryComponents: [ItemListElementComponent]
}
}).compileComponents();
}));
@@ -48,7 +48,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
comp.object = new TestType();
comp.viewMode = testViewMode;
comp.context = testContext;
spyOnExported(listableObjectDecorators, 'getListableObjectComponent').and.returnValue(PublicationListElementComponent);
spyOnExported(listableObjectDecorators, 'getListableObjectComponent').and.returnValue(ItemListElementComponent);
fixture.detectChanges();
}));

View File

@@ -0,0 +1,4 @@
<ds-item-search-result-grid-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType">
<ng-content></ng-content>
<ng-content></ng-content>
</ds-item-search-result-grid-element>

View File

@@ -4,7 +4,7 @@ import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { PublicationGridElementComponent } from './publication-grid-element.component';
import { ItemGridElementComponent } from './item-grid-element.component';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
@@ -41,7 +41,7 @@ const mockItem = Object.assign(new Item(), {
}
});
describe('PublicationGridElementComponent', () => {
describe('ItemGridElementComponent', () => {
let comp;
let fixture;
@@ -52,18 +52,18 @@ describe('PublicationGridElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [PublicationGridElementComponent, TruncatePipe],
declarations: [ItemGridElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PublicationGridElementComponent, {
}).overrideComponent(ItemGridElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(PublicationGridElementComponent);
fixture = TestBed.createComponent(ItemGridElementComponent);
comp = fixture.componentInstance;
}));
@@ -74,7 +74,7 @@ describe('PublicationGridElementComponent', () => {
});
it(`should contain a PublicationGridElementComponent`, () => {
const publicationGridElement = fixture.debugElement.query(By.css(`ds-publication-search-result-grid-element`));
const publicationGridElement = fixture.debugElement.query(By.css(`ds-item-search-result-grid-element`));
expect(publicationGridElement).not.toBeNull();
});
});

View File

@@ -8,13 +8,13 @@ import { Item } from '../../../../../core/shared/item.model';
@listableObjectComponent('Publication', ViewMode.GridElement)
@listableObjectComponent(Item, ViewMode.GridElement)
@Component({
selector: 'ds-publication-grid-element',
styleUrls: ['./publication-grid-element.component.scss'],
templateUrl: './publication-grid-element.component.html',
selector: 'ds-item-grid-element',
styleUrls: ['./item-grid-element.component.scss'],
templateUrl: './item-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Publication
*/
export class PublicationGridElementComponent extends AbstractListableElementComponent<Item> {
export class ItemGridElementComponent extends AbstractListableElementComponent<Item> {
}

View File

@@ -1,4 +0,0 @@
<ds-publication-search-result-grid-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType">
<ng-content></ng-content>
<ng-content></ng-content>
</ds-publication-search-result-grid-element>

View File

@@ -8,6 +8,7 @@
</ds-grid-thumbnail>
</span>
<div class="card-body">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<h4 class="card-title">{{dso.name}}</h4>
<p *ngIf="dso.shortDescription" class="card-text">{{dso.shortDescription}}</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">

View File

@@ -8,6 +8,7 @@
</ds-grid-thumbnail>
</span>
<div class="card-body">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<h4 class="card-title">{{dso.name}}</h4>
<p *ngIf="dso.shortDescription" class="card-text">{{dso.shortDescription}}</p>
<div *ngIf="linkType != linkTypes.None" class="text-center">

View File

@@ -17,7 +17,7 @@
</div>
</span>
<div class="card-body">
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>

View File

@@ -24,7 +24,7 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { PublicationSearchResultGridElementComponent } from './publication-search-result-grid-element.component';
import { ItemSearchResultGridElementComponent } from './item-search-result-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
@@ -72,7 +72,7 @@ mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
}
});
describe('PublicationGridElementComponent', getEntityGridElementTestComponent(PublicationSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['authors', 'date', 'abstract']));
describe('ItemGridElementComponent', getEntityGridElementTestComponent(ItemSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['authors', 'date', 'abstract']));
/**
* Create test cases for a grid component of an entity.

View File

@@ -9,13 +9,13 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear
@listableObjectComponent('PublicationSearchResult', ViewMode.GridElement)
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement)
@Component({
selector: 'ds-publication-search-result-grid-element',
styleUrls: ['./publication-search-result-grid-element.component.scss'],
templateUrl: './publication-search-result-grid-element.component.html',
selector: 'ds-item-search-result-grid-element',
styleUrls: ['./item-search-result-grid-element.component.scss'],
templateUrl: './item-search-result-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item search result of the type Publication
*/
export class PublicationSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
}

View File

@@ -0,0 +1 @@
<ds-item-search-result-list-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType"></ds-item-search-result-list-element>

View File

@@ -1,7 +1,7 @@
import { async, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { PublicationListElementComponent } from './publication-list-element.component';
import { ItemListElementComponent } from './item-list-element.component';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
@@ -43,7 +43,7 @@ const mockItem: Item = Object.assign(new Item(), {
}
});
describe('PublicationListElementComponent', () => {
describe('ItemListElementComponent', () => {
let comp;
let fixture;
@@ -53,18 +53,18 @@ describe('PublicationListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PublicationListElementComponent, TruncatePipe],
declarations: [ItemListElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PublicationListElementComponent, {
}).overrideComponent(ItemListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(PublicationListElementComponent);
fixture = TestBed.createComponent(ItemListElementComponent);
comp = fixture.componentInstance;
}));
@@ -75,7 +75,7 @@ describe('PublicationListElementComponent', () => {
});
it(`should contain a PublicationListElementComponent`, () => {
const publicationListElement = fixture.debugElement.query(By.css(`ds-publication-search-result-list-element`));
const publicationListElement = fixture.debugElement.query(By.css(`ds-item-search-result-list-element`));
expect(publicationListElement).not.toBeNull();
});
});

View File

@@ -7,12 +7,12 @@ import { Item } from '../../../../../core/shared/item.model';
@listableObjectComponent('Publication', ViewMode.ListElement)
@listableObjectComponent(Item, ViewMode.ListElement)
@Component({
selector: 'ds-publication-list-element',
styleUrls: ['./publication-list-element.component.scss'],
templateUrl: './publication-list-element.component.html'
selector: 'ds-item-list-element',
styleUrls: ['./item-list-element.component.scss'],
templateUrl: './item-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Publication
*/
export class PublicationListElementComponent extends AbstractListableElementComponent<Item> {
export class ItemListElementComponent extends AbstractListableElementComponent<Item> {
}

View File

@@ -1 +0,0 @@
<ds-publication-search-result-list-element [showLabel]="showLabel" [object]="{ indexableObject: object, hitHighlights: {} }" [linkType]="linkType"></ds-publication-search-result-list-element>

View File

@@ -1,3 +0,0 @@
<div *ngIf="object && object.firstMetadataValue('relationship.type') as type">
<span class="badge badge-light">{{ type.toLowerCase() + '.listelement.badge' | translate }}</span>
</div>

View File

@@ -1,16 +0,0 @@
import { Component, Input } from '@angular/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
@Component({
selector: 'ds-item-type-badge',
templateUrl: './item-type-badge.component.html'
})
/**
* Component rendering the type of an item as a badge
*/
export class ItemTypeBadgeComponent {
/**
* The component used to retrieve the type from
*/
@Input() object: DSpaceObject;
}

View File

@@ -2,7 +2,7 @@
<ng-container *ngIf="status">
<ds-mydspace-item-status [status]="status"></ds-mydspace-item-status>
</ng-container>
<ds-item-type-badge [object]="item"></ds-item-type-badge>
<ds-type-badge [object]="item"></ds-type-badge>
<ds-truncatable [id]="item.id">
<h3 [innerHTML]="item.firstMetadataValue('dc.title') || ('mydspace.results.no-title' | translate)" [ngClass]="{'lead': true,'text-muted': !item.firstMetadataValue('dc.title')}"></h3>
<div>

View File

@@ -153,7 +153,7 @@ describe('ItemListPreviewComponent', () => {
});
it('should show the entity type span', () => {
const entityField = fixture.debugElement.query(By.css('ds-item-type-badge'));
const entityField = fixture.debugElement.query(By.css('ds-type-badge'));
expect(entityField).not.toBeNull();
});
});

View File

@@ -1,3 +1,4 @@
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></a>
<span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></span>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>

View File

@@ -1,3 +1,4 @@
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></a>
<span *ngIf="linkType == linkTypes.None" class="lead" [innerHTML]="firstMetadataValue('dc.title')"></span>
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="firstMetadataValue('dc.description.abstract')"></div>

View File

@@ -1,4 +1,4 @@
<ds-item-type-badge *ngIf="showLabel" [object]="dso"></ds-item-type-badge>
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id" *ngIf="object !== undefined && object !== null">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"

View File

@@ -2,14 +2,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs';
import { PublicationSearchResultListElementComponent } from './publication-search-result-list-element.component';
import { ItemSearchResultListElementComponent } from './item-search-result-list-element.component';
import { Item } from '../../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../../truncatable/truncatable.service';
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
let publicationListElementComponent: PublicationSearchResultListElementComponent;
let fixture: ComponentFixture<PublicationSearchResultListElementComponent>;
let publicationListElementComponent: ItemSearchResultListElementComponent;
let fixture: ComponentFixture<ItemSearchResultListElementComponent>;
const mockItemWithMetadata: ItemSearchResult = Object.assign(new ItemSearchResult(), {
indexableObject:
@@ -64,22 +64,22 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(new ItemSearchRe
})
});
describe('PublicationListElementComponent', () => {
describe('ItemListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PublicationSearchResultListElementComponent, TruncatePipe],
declarations: [ItemSearchResultListElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: {} }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PublicationSearchResultListElementComponent, {
}).overrideComponent(ItemSearchResultListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(PublicationSearchResultListElementComponent);
fixture = TestBed.createComponent(ItemSearchResultListElementComponent);
publicationListElementComponent = fixture.componentInstance;
}));

View File

@@ -8,12 +8,12 @@ import { Item } from '../../../../../../core/shared/item.model';
@listableObjectComponent('PublicationSearchResult', ViewMode.ListElement)
@listableObjectComponent(ItemSearchResult, ViewMode.ListElement)
@Component({
selector: 'ds-publication-search-result-list-element',
styleUrls: ['./publication-search-result-list-element.component.scss'],
templateUrl: './publication-search-result-list-element.component.html'
selector: 'ds-item-search-result-list-element',
styleUrls: ['./item-search-result-list-element.component.scss'],
templateUrl: './item-search-result-list-element.component.html'
})
/**
* The component for displaying a list element for an item search result of the type Publication
*/
export class PublicationSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
}

View File

@@ -0,0 +1,3 @@
<div *ngIf="typeMessage">
<span class="badge badge-light">{{ typeMessage | translate }}</span>
</div>

View File

@@ -5,11 +5,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatePipe } from '../../utils/truncate.pipe';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ItemTypeBadgeComponent } from './item-type-badge.component';
import { By } from '@angular/platform-browser';
import { TypeBadgeComponent } from './type-badge.component';
let comp: ItemTypeBadgeComponent;
let fixture: ComponentFixture<ItemTypeBadgeComponent>;
let comp: TypeBadgeComponent;
let fixture: ComponentFixture<TypeBadgeComponent>;
const type = 'authorOfPublication';
@@ -41,15 +41,15 @@ describe('ItemTypeBadgeComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ItemTypeBadgeComponent, TruncatePipe],
declarations: [TypeBadgeComponent, TruncatePipe],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemTypeBadgeComponent, {
}).overrideComponent(TypeBadgeComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemTypeBadgeComponent);
fixture = TestBed.createComponent(TypeBadgeComponent);
comp = fixture.componentInstance;
}));
@@ -71,9 +71,9 @@ describe('ItemTypeBadgeComponent', () => {
fixture.detectChanges();
});
it('should not show a badge', () => {
it('should show an item badge', () => {
const badge = fixture.debugElement.query(By.css('span.badge'));
expect(badge).toBeNull();
expect(badge.nativeElement.textContent).toContain('item');
});
});
});

View File

@@ -0,0 +1,47 @@
import { Component, Input } from '@angular/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { hasValue, isEmpty } from '../../empty.util';
import { getResourceTypeValueFor } from '../../../core/cache/object-cache.reducer';
@Component({
selector: 'ds-type-badge',
templateUrl: './type-badge.component.html'
})
/**
* Component rendering the type of an item as a badge
*/
export class TypeBadgeComponent {
private _object: DSpaceObject;
private _typeMessage: string;
/**
* The component used to retrieve the type from
*/
@Input() set object(object: DSpaceObject) {
this._object = object;
const renderTypes = this._object.getRenderTypes();
if (!isEmpty(renderTypes.length)) {
const renderType = renderTypes[0];
if (renderType instanceof Function) {
const resourceTypeValue = getResourceTypeValueFor(object.type);
if (hasValue(resourceTypeValue)) {
this._typeMessage = `${resourceTypeValue.toLowerCase()}.listelement.badge`;
} else {
this._typeMessage = `${renderType.name.toLowerCase()}.listelement.badge`;
}
} else {
this._typeMessage = `${renderType.toLowerCase()}.listelement.badge`;
}
}
}
get object(): DSpaceObject {
return this._object;
}
get typeMessage(): string {
return this._typeMessage;
}
}

View File

@@ -152,29 +152,33 @@ describe('ResourcePolicyCreateComponent test suite', () => {
fixture.destroy();
});
it('should init component properly', () => {
it('should init component properly', (done) => {
fixture.detectChanges();
expect(compAsAny.targetResourceUUID).toBe('itemUUID');
expect(compAsAny.targetResourceName).toBe('test item');
done();
});
it('should redirect to authorizations page', () => {
it('should redirect to authorizations page', (done) => {
comp.redirectToAuthorizationsPage();
expect(compAsAny.router.navigate).toHaveBeenCalled();
done();
});
it('should return true when is Processing', () => {
it('should return true when is Processing', (done) => {
compAsAny.processing$.next(true);
expect(comp.isProcessing()).toBeObservable(cold('a', {
a: true
}));
done();
});
it('should return false when is not Processing', () => {
it('should return false when is not Processing', (done) => {
compAsAny.processing$.next(false);
expect(comp.isProcessing()).toBeObservable(cold('a', {
a: false
}));
done();
});
describe('when target type is group', () => {

View File

@@ -101,7 +101,9 @@ export class ResourcePolicyCreateComponent implements OnInit {
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
this.processing$.next(false);
if (responseRD.hasSucceeded) {
// NOTE Currently due to a bug a successful 201 response has failed
// TODO review it when https://github.com/DSpace/dspace-angular/issues/739 is fixed
if (responseRD.hasSucceeded || responseRD.statusCode === 201) {
this.notificationsService.success(null, this.translate.get('resource-policies.create.page.success.content'));
this.redirectToAuthorizationsPage();
} else {

View File

@@ -137,28 +137,32 @@ describe('ResourcePolicyEditComponent test suite', () => {
fixture.destroy();
});
it('should init component properly', () => {
it('should init component properly', (done) => {
fixture.detectChanges();
expect(compAsAny.resourcePolicy).toEqual(resourcePolicy);
done();
});
it('should redirect to authorizations page', () => {
it('should redirect to authorizations page', (done) => {
comp.redirectToAuthorizationsPage();
expect(compAsAny.router.navigate).toHaveBeenCalled();
done();
});
it('should return true when is Processing', () => {
it('should return true when is Processing', (done) => {
compAsAny.processing$.next(true);
expect(comp.isProcessing()).toBeObservable(cold('a', {
a: true
}));
done();
});
it('should return false when is not Processing', () => {
it('should return false when is not Processing', (done) => {
compAsAny.processing$.next(false);
expect(comp.isProcessing()).toBeObservable(cold('a', {
a: false
}));
done();
});
describe('', () => {

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { first, map, take } from 'rxjs/operators';
import { map, take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
@@ -12,6 +12,7 @@ import { ResourcePolicy } from '../../../core/resource-policy/models/resource-po
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing-paths';
import { getSucceededRemoteWithNotEmptyDataOrFailed } from '../../../core/shared/operators';
@Component({
selector: 'ds-resource-policy-edit',
@@ -88,10 +89,11 @@ export class ResourcePolicyEditComponent implements OnInit {
_links: this.resourcePolicy._links
});
this.resourcePolicyService.update(updatedObject).pipe(
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
getSucceededRemoteWithNotEmptyDataOrFailed(),
take(1)
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
this.processing$.next(false);
if (responseRD.hasSucceeded) {
if (responseRD && responseRD.hasSucceeded) {
this.notificationsService.success(null, this.translate.get('resource-policies.edit.page.success.content'));
this.redirectToAuthorizationsPage();
} else {

View File

@@ -1,9 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { FilterType } from '../../../filter-type.model';
import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
import { renderFacetFor } from '../search-filter-type-decorator';
import { FacetValue } from '../../../facet-value.model';
@Component({
selector: 'ds-search-authority-filter',
@@ -17,20 +15,4 @@ import { FacetValue } from '../../../facet-value.model';
*/
@renderFacetFor(FilterType.authority)
export class SearchAuthorityFilterComponent extends SearchFacetFilterComponent implements OnInit {
/**
* TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
* Retrieve facet value from search link
*/
protected getFacetValue(facet: FacetValue): string {
const search = facet._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
}
}

View File

@@ -130,7 +130,7 @@ describe('SearchFacetOptionComponent', () => {
comp.addQueryParams = {};
(comp as any).updateAddParams(selectedValues);
expect(comp.addQueryParams).toEqual({
[mockFilterConfig.paramName]: [value1, value.value],
[mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'],
page: 1
});
});
@@ -145,7 +145,7 @@ describe('SearchFacetOptionComponent', () => {
comp.addQueryParams = {};
(comp as any).updateAddParams(selectedValues);
expect(comp.addQueryParams).toEqual({
[mockAuthorityFilterConfig.paramName]: [value1, `${value2},${operator}`],
[mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`],
page: 1
});
});

View File

@@ -8,8 +8,8 @@ import { SearchService } from '../../../../../../core/shared/search/search.servi
import { SearchFilterService } from '../../../../../../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { hasValue } from '../../../../../empty.util';
import { FilterType } from '../../../../filter-type.model';
import { currentPath } from '../../../../../utils/route.utils';
import { getFacetValueForType } from '../../../../search.utils';
@Component({
selector: 'ds-search-facet-option',
@@ -102,7 +102,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
*/
private updateAddParams(selectedValues: FacetValue[]): void {
this.addQueryParams = {
[this.filterConfig.paramName]: [...selectedValues.map((facetValue: FacetValue) => facetValue.label), this.getFacetValue()],
[this.filterConfig.paramName]: [...selectedValues.map((facetValue: FacetValue) => getFacetValueForType(facetValue, this.filterConfig)), this.getFacetValue()],
page: 1
};
}
@@ -112,19 +112,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
* Retrieve facet value related to facet type
*/
private getFacetValue(): string {
if (this.filterConfig.type === FilterType.authority) {
const search = this.filterValue._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
} else {
return this.filterValue.value;
}
return getFacetValueForType(this.filterValue, this.filterConfig);
}
/**

View File

@@ -3,6 +3,6 @@
[queryParams]="removeQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize">
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.value} }}
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.label} }}
</span>
</a>

View File

@@ -7,8 +7,8 @@ import { SearchFilterService } from '../../../../../../core/shared/search/search
import { hasValue } from '../../../../../empty.util';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { FacetValue } from '../../../../facet-value.model';
import { FilterType } from '../../../../filter-type.model';
import { currentPath } from '../../../../../utils/route.utils';
import { getFacetValueForType } from '../../../../search.utils';
@Component({
selector: 'ds-search-facet-selected-option',
@@ -101,19 +101,7 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy {
* Retrieve facet value related to facet type
*/
private getFacetValue(facetValue: FacetValue): string {
if (this.filterConfig.type === FilterType.authority) {
const search = facetValue._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
} else {
return facetValue.value;
}
return getFacetValueForType(facetValue, this.filterConfig);
}
/**

View File

@@ -208,7 +208,7 @@ describe('SearchFacetFilterComponent', () => {
it('should call navigate on the router with the right searchlink and parameters', () => {
expect(router.navigate).toHaveBeenCalledWith(searchUrl.split('/'), {
queryParams: { [mockFilterConfig.paramName]: [...selectedValues, testValue] },
queryParams: { [mockFilterConfig.paramName]: [...selectedValues.map((value) => `${value},equals`), testValue] },
queryParamsHandling: 'merge'
});
});

View File

@@ -25,6 +25,7 @@ import { InputSuggestion } from '../../../../input-suggestions/input-suggestions
import { SearchOptions } from '../../../search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
import { currentPath } from '../../../../utils/route.utils';
import { getFacetValueForType, stripOperatorFromFilterValue } from '../../../search.utils';
@Component({
selector: 'ds-search-facet-filter',
@@ -148,7 +149,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
if (hasValue(fValue)) {
return fValue;
}
return Object.assign(new FacetValue(), { label: value, value: value });
return Object.assign(new FacetValue(), { label: stripOperatorFromFilterValue(value), value: value });
});
})
);
@@ -287,7 +288,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
return rd.payload.page.map((facet) => {
return {
displayValue: this.getDisplayValue(facet, data),
value: this.getFacetValue(facet)
value: stripOperatorFromFilterValue(this.getFacetValue(facet))
}
})
}
@@ -303,7 +304,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
* Retrieve facet value
*/
protected getFacetValue(facet: FacetValue): string {
return facet.value;
return getFacetValueForType(facet, this.filterConfig);
}
/**

View File

@@ -5,6 +5,9 @@ import {
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
import { renderFacetFor } from '../search-filter-type-decorator';
import {
addOperatorToFilterValue,
} from '../../../search.utils';
/**
* This component renders a simple item page.
@@ -24,4 +27,12 @@ import { renderFacetFor } from '../search-filter-type-decorator';
*/
@renderFacetFor(FilterType.text)
export class SearchTextFilterComponent extends SearchFacetFilterComponent implements OnInit {
/**
* Submits a new active custom value to the filter from the input field
* Overwritten method from parent component, adds the "query" operator to the received data before passing it on
* @param data The string from the input field
*/
onSubmit(data: any) {
super.onSubmit(addOperatorToFilterValue(data, 'query'));
}
}

View File

@@ -3,6 +3,8 @@ import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.c
import { Observable } from 'rxjs';
import { Params, Router } from '@angular/router';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { map } from 'rxjs/operators';
import { stripOperatorFromFilterValue } from '../search.utils';
@Component({
selector: 'ds-search-labels',
@@ -30,6 +32,15 @@ export class SearchLabelsComponent {
constructor(
protected router: Router,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters();
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters().pipe(
map((params) => {
const labels = {};
Object.keys(params)
.forEach((key) => {
labels[key] = [...params[key].map((value) => stripOperatorFromFilterValue(value))];
});
return labels;
})
);
}
}

View File

@@ -0,0 +1,52 @@
import { FacetValue } from './facet-value.model';
import { SearchFilterConfig } from './search-filter-config.model';
import { addOperatorToFilterValue, getFacetValueForType, stripOperatorFromFilterValue } from './search.utils';
describe('Search Utils', () => {
describe('getFacetValueForType', () => {
let facetValueWithSearchHref: FacetValue;
let facetValueWithoutSearchHref: FacetValue;
let searchFilterConfig: SearchFilterConfig;
beforeEach(() => {
facetValueWithSearchHref = Object.assign(new FacetValue(), {
value: 'Value with search href',
_links: {
search: {
href: 'rest/api/search?f.otherFacet=Other facet value,operator&f.facetName=Value with search href,operator'
}
}
});
facetValueWithoutSearchHref = Object.assign(new FacetValue(), {
value: 'Value without search href'
});
searchFilterConfig = Object.assign(new SearchFilterConfig(), {
name: 'facetName'
});
});
it('should retrieve the correct value from the search href', () => {
expect(getFacetValueForType(facetValueWithSearchHref, searchFilterConfig)).toEqual('Value with search href,operator');
});
it('should return the facet value with an equals operator by default', () => {
expect(getFacetValueForType(facetValueWithoutSearchHref, searchFilterConfig)).toEqual('Value without search href,equals');
});
});
describe('stripOperatorFromFilterValue', () => {
it('should strip the operator from the value', () => {
expect(stripOperatorFromFilterValue('value,operator')).toEqual('value');
});
});
describe('addOperatorToFilterValue', () => {
it('should add the operator to the value', () => {
expect(addOperatorToFilterValue('value', 'operator')).toEqual('value,operator');
});
it('shouldn\'t add the operator to the value if it already contains the operator', () => {
expect(addOperatorToFilterValue('value,operator', 'operator')).toEqual('value,operator');
});
});
});

View File

@@ -0,0 +1,44 @@
import { FacetValue } from './facet-value.model';
import { SearchFilterConfig } from './search-filter-config.model';
import { isNotEmpty } from '../empty.util';
/**
* Get a facet's value by matching its parameter in the search href, this will include the operator of the facet value
* If the {@link FacetValue} doesn't contain a search link, its raw value will be returned as a fallback
* @param facetValue
* @param searchFilterConfig
*/
export function getFacetValueForType(facetValue: FacetValue, searchFilterConfig: SearchFilterConfig): string {
const regex = new RegExp(`[?|&]${searchFilterConfig.paramName}=(${facetValue.value}[^&]*)`, 'g');
if (isNotEmpty(facetValue._links)) {
const values = regex.exec(facetValue._links.search.href);
if (isNotEmpty(values)) {
return values[1];
}
}
return addOperatorToFilterValue(facetValue.value, 'equals');
}
/**
* Strip the operator from a filter value
* Warning: This expects the value to end with an operator, otherwise it might strip unwanted content
* @param value
*/
export function stripOperatorFromFilterValue(value: string) {
if (value.lastIndexOf(',') > -1) {
return value.substring(0, value.lastIndexOf(','));
}
return value;
}
/**
* Add an operator to a string
* @param value
* @param operator
*/
export function addOperatorToFilterValue(value: string, operator: string) {
if (!value.endsWith(`,${operator}`)) {
return `${value},${operator}`;
}
return value;
}

View File

@@ -21,7 +21,7 @@ import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role
import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component';
import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component';
import { ItemListElementComponent } from './object-list/item-list-element/item-types/item/item-list-element.component';
import { EnumKeysPipe } from './utils/enum-keys-pipe';
import { FileSizePipe } from './utils/file-size-pipe';
import { MetadataFieldValidator } from './utils/metadatafield-validator.directive';
@@ -160,13 +160,12 @@ import { ItemSelectComponent } from './object-select/item-select/item-select.com
import { CollectionSelectComponent } from './object-select/collection-select/collection-select.component';
import { FilterInputSuggestionsComponent } from './input-suggestions/filter-suggestions/filter-input-suggestions.component';
import { DsoInputSuggestionsComponent } from './input-suggestions/dso-input-suggestions/dso-input-suggestions.component';
import { PublicationGridElementComponent } from './object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
import { ItemGridElementComponent } from './object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { TypeBadgeComponent } from './object-list/type-badge/type-badge.component';
import { MetadataRepresentationLoaderComponent } from './metadata-representation/metadata-representation-loader.component';
import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive';
import { ListableObjectComponentLoaderComponent } from './object-collection/shared/listable-object/listable-object-component-loader.component';
import { PublicationSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-types/publication/publication-search-result-list-element.component';
import { PublicationSearchResultGridElementComponent } from './object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component';
import { ItemSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive';
import { SearchLabelComponent } from './search/search-labels/search-label/search-label.component';
import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
@@ -219,6 +218,9 @@ import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-
import { HoverClassDirective } from './hover-class.directive';
import { ValidationSuggestionsComponent } from './input-suggestions/validation-suggestions/validation-suggestions.component';
import { ItemAlertsComponent } from './item/item-alerts/item-alerts.component';
import { ItemSearchResultGridElementComponent } from './object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component';
import { ResourcePolicyEditComponent } from './resource-policies/edit/resource-policy-edit.component';
import { ResourcePolicyCreateComponent } from './resource-policies/create/resource-policy-create.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -384,7 +386,7 @@ const COMPONENTS = [
BrowseByComponent,
AbstractTrackableComponent,
ComcolMetadataComponent,
ItemTypeBadgeComponent,
TypeBadgeComponent,
BrowseByComponent,
AbstractTrackableComponent,
CustomSwitchComponent,
@@ -400,12 +402,14 @@ const COMPONENTS = [
LogInPasswordComponent,
LogInContainerComponent,
ItemVersionsComponent,
PublicationSearchResultListElementComponent,
ItemSearchResultListElementComponent,
ItemVersionsNoticeComponent,
ModifyItemOverviewComponent,
ImpersonateNavbarComponent,
ResourcePoliciesComponent,
ResourcePolicyFormComponent,
ResourcePolicyEditComponent,
ResourcePolicyCreateComponent,
EpersonGroupListComponent,
EpersonSearchBoxComponent,
GroupSearchBoxComponent,
@@ -429,10 +433,10 @@ const ENTRY_COMPONENTS = [
CommunitySearchResultGridElementComponent,
CollectionSearchResultGridElementComponent,
SearchResultGridElementComponent,
PublicationListElementComponent,
PublicationGridElementComponent,
PublicationSearchResultListElementComponent,
PublicationSearchResultGridElementComponent,
ItemListElementComponent,
ItemGridElementComponent,
ItemSearchResultListElementComponent,
ItemSearchResultGridElementComponent,
BrowseEntryListElementComponent,
SearchResultDetailElementComponent,
SearchResultGridElementComponent,

View File

@@ -6,13 +6,13 @@ describe('Relation Query Utils', () => {
describe('getQueryByRelations', () => {
it('Should return the correct query based on relationtype and uuid', () => {
const result = getQueryByRelations(relationtype, itemUUID);
expect(result).toEqual('query=relation.isAuthorOfPublication:a7939af0-36ad-430d-af09-7be8b0a4dadd');
expect(result).toEqual('query=relation.isAuthorOfPublication:"a7939af0-36ad-430d-af09-7be8b0a4dadd"');
});
});
describe('getFilterByRelation', () => {
it('Should return the correct query based on relationtype and uuid', () => {
const result = getFilterByRelation(relationtype, itemUUID);
expect(result).toEqual('f.isAuthorOfPublication=a7939af0-36ad-430d-af09-7be8b0a4dadd');
expect(result).toEqual('f.isAuthorOfPublication=a7939af0-36ad-430d-af09-7be8b0a4dadd,equals');
});
});
});

View File

@@ -5,7 +5,7 @@
* @returns {string} Query
*/
export function getQueryByRelations(relationType: string, itemUUID: string): string {
return `query=relation.${relationType}:${itemUUID}`;
return `query=relation.${relationType}:"${itemUUID}"`;
}
/**
@@ -14,5 +14,5 @@ export function getQueryByRelations(relationType: string, itemUUID: string): str
* @param itemUUID The item's UUID
*/
export function getFilterByRelation(relationType: string, itemUUID: string): string {
return `f.${relationType}=${itemUUID}`;
return `f.${relationType}=${itemUUID},equals`;
}

View File

@@ -711,6 +711,10 @@
"collection.edit.tabs.curate.title": "Collection Edit - Curate",
"collection.edit.tabs.authorizations.head": "Authorizations",
"collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations",
"collection.edit.tabs.metadata.head": "Edit Metadata",
"collection.edit.tabs.metadata.title": "Collection Edit - Metadata",
@@ -797,6 +801,10 @@
"collection.listelement.badge": "Collection",
"collection.page.browse.recent.head": "Recent Submissions",
"collection.page.browse.recent.empty": "No items to show",
@@ -898,6 +906,14 @@
"community.edit.tabs.roles.title": "Community Edit - Roles",
"community.edit.tabs.authorizations.head": "Authorizations",
"community.edit.tabs.authorizations.title": "Community Edit - Authorizations",
"community.listelement.badge": "Community",
"comcol-role.edit.no-group": "None",
@@ -1725,6 +1741,28 @@
"item.listelement.badge": "Item",
"item.page.description": "Description",
"item.page.edit": "Edit this item",
"item.page.journal-issn": "Journal ISSN",
"item.page.journal-title": "Journal Title",
"item.page.publisher": "Publisher",
"item.page.titleprefix": "Item: ",
"item.page.volume-title": "Volume Title",
"item.search.results.head": "Item Search Results",
"item.search.title": "DSpace Angular :: Item Search",
"item.page.abstract": "Abstract",
"item.page.author": "Authors",
@@ -2658,6 +2696,10 @@
"resource-policies.add.for.item": "Add a new Item policy",
"resource-policies.add.for.community": "Add a new Community policy",
"resource-policies.add.for.collection": "Add a new Collection policy",
"resource-policies.create.page.heading": "Create new resource policy for ",
"resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.",
@@ -2740,6 +2782,10 @@
"resource-policies.table.headers.title.for.item": "Policies for Item",
"resource-policies.table.headers.title.for.community": "Policies for Community",
"resource-policies.table.headers.title.for.collection": "Policies for Collection",
"search.description": "",