From cc862af2498885257a0e6187a2e3b761c0b33450 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 2 Apr 2019 17:36:51 +0200 Subject: [PATCH 01/60] 61142: Item relations initial edit page --- resources/i18n/en.json | 4 + .../edit-item-page/edit-item-page.module.ts | 6 +- .../edit-item-page.routing.module.ts | 6 + .../edit-in-place-relationship.component.html | 19 ++ .../edit-in-place-relationship.component.scss | 15 ++ ...it-in-place-relationship.component.spec.ts | 0 .../edit-in-place-relationship.component.ts | 64 +++++ .../item-relationships.component.html | 33 +++ .../item-relationships.component.scss | 22 ++ .../item-relationships.component.spec.ts | 0 .../item-relationships.component.ts | 228 ++++++++++++++++++ .../object-updates/object-updates.service.ts | 15 ++ 12 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts create mode 100644 src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html create mode 100644 src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss create mode 100644 src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 1952e345d8..8b7d15abfd 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -175,6 +175,10 @@ "head": "Item Metadata", "title": "Item Edit - Metadata" }, + "relationships": { + "head": "Item Relationships", + "title": "Item Edit - Relationships" + }, "view": { "head": "View Item", "title": "Item Edit - View" diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index 0c1de642ce..079f065d5f 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -15,6 +15,8 @@ import { ItemDeleteComponent } from './item-delete/item-delete.component'; import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; +import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; +import { EditInPlaceRelationshipComponent } from './item-relationships/edit-in-place-relationship/edit-in-place-relationship.component'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -37,8 +39,10 @@ import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.compo ItemDeleteComponent, ItemStatusComponent, ItemMetadataComponent, + ItemRelationshipsComponent, ItemBitstreamsComponent, - EditInPlaceFieldComponent + EditInPlaceFieldComponent, + EditInPlaceRelationshipComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index 223b5f7c8e..55c5bcb747 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -10,6 +10,7 @@ import { ItemDeleteComponent } from './item-delete/item-delete.component'; import { ItemStatusComponent } from './item-status/item-status.component'; import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; +import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; const ITEM_EDIT_WITHDRAW_PATH = 'withdraw'; const ITEM_EDIT_REINSTATE_PATH = 'reinstate'; @@ -49,6 +50,11 @@ const ITEM_EDIT_DELETE_PATH = 'delete'; component: ItemMetadataComponent, data: { title: 'item.edit.tabs.metadata.title' } }, + { + path: 'relationships', + component: ItemRelationshipsComponent, + data: { title: 'item.edit.tabs.relationships.title' } + }, { path: 'view', /* TODO - change when view page exists */ diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html new file mode 100644 index 0000000000..84c645d1a8 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html @@ -0,0 +1,19 @@ +
+
+ +
+
+
+ + +
+
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss new file mode 100644 index 0000000000..808a8344ba --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss @@ -0,0 +1,15 @@ +@import '../../../../../styles/variables.scss'; + +.btn[disabled] { + color: $gray-600; + border-color: $gray-600; + z-index: 0; // prevent border colors jumping on hover +} + +.relationship-action-buttons { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts new file mode 100644 index 0000000000..bb29f7a889 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts @@ -0,0 +1,64 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; +import { cloneDeep } from 'lodash'; +import { Item } from '../../../../core/shared/item.model'; +import { VIEW_MODE_ELEMENT } from '../../../simple/related-items/related-items-component'; +import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; +import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; +import { Observable } from 'rxjs/internal/Observable'; +import { map } from 'rxjs/operators'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: '[ds-edit-in-place-relationship]', + styleUrls: ['./edit-in-place-relationship.component.scss'], + templateUrl: './edit-in-place-relationship.component.html', +}) +export class EditInPlaceRelationshipComponent implements OnChanges { + /** + * The current field, value and state of the relationship + */ + @Input() fieldUpdate: FieldUpdate; + + /** + * The current url of this page + */ + @Input() url: string; + + /** + * The related item of this relationship + */ + item: Item; + + /** + * The view-mode we're currently on + */ + viewMode = VIEW_MODE_ELEMENT; + + constructor(private objectUpdatesService: ObjectUpdatesService) { + } + + /** + * Sets the current relationship based on the fieldUpdate input field + */ + ngOnChanges(): void { + this.item = cloneDeep(this.fieldUpdate.field) as Item; + } + + remove(): void { + this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.item); + } + + undo(): void { + this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.item.uuid); + } + + canRemove(): boolean { + return this.fieldUpdate.changeType !== FieldChangeType.REMOVE; + } + + canUndo(): boolean { + return this.fieldUpdate.changeType >= 0; + } + +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html new file mode 100644 index 0000000000..c95812b4bb --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -0,0 +1,33 @@ +
+
+ + + +
+
+
{{label}}
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss new file mode 100644 index 0000000000..898533a9f0 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss @@ -0,0 +1,22 @@ +@import '../../../../styles/variables.scss'; + +.button-row { + .btn { + margin-right: 0.5 * $spacer; + + &:last-child { + margin-right: 0; + } + + @media screen and (min-width: map-get($grid-breakpoints, sm)) { + min-width: $edit-item-button-min-width; + } + } + + &.top .btn { + margin-top: $spacer/2; + margin-bottom: $spacer/2; + } + + +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts new file mode 100644 index 0000000000..0bd1635ae1 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -0,0 +1,228 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { Item } from '../../../core/shared/item.model'; +import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; +import { Observable } from 'rxjs/internal/Observable'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { distinctUntilChanged, filter, first, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators'; +import { zip as observableZip } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; +import { hasValue, hasValueOperator } from '../../../shared/empty.util'; +import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; +import { + compareArraysUsingIds, + filterRelationsByTypeLabel, + relationsToItems +} from '../../simple/item-types/shared/item.component'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { TranslateService } from '@ngx-translate/core'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; + +@Component({ + selector: 'ds-item-relationships', + styleUrls: ['./item-relationships.component.scss'], + templateUrl: './item-relationships.component.html', +}) +export class ItemRelationshipsComponent implements OnInit { + + /** + * The item to display the edit page for + */ + item: Item; + /** + * The current values and updates for all this item's metadata fields + */ + updates$: Observable; + /** + * The current url of this page + */ + url: string; + /** + * Prefix for this component's notification translate keys + */ + private notificationsPrefix = 'item.edit.metadata.notifications.'; + + /** + * The labels of all different relations within this item + */ + relationLabels$: Observable; + + /** + * Resolved relationships and types together in one observable + */ + resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>; + /** + * The time span for being able to undo discarding changes + */ + private discardTimeOut: number; + + constructor(private route: ActivatedRoute, + private router: Router, + private translateService: TranslateService, + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + private objectUpdatesService: ObjectUpdatesService, + private notificationsService: NotificationsService, + private itemDataService: ItemDataService) { + } + + ngOnInit(): void { + this.route.parent.data.pipe(map((data) => data.item)) + .pipe( + first(), + map((data: RemoteData) => data.payload) + ).subscribe((item: Item) => { + this.item = item; + }); + this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; + this.url = this.router.url; + if (this.url.indexOf('?') > 0) { + this.url = this.url.substr(0, this.url.indexOf('?')); + } + this.hasChanges().pipe(first()).subscribe((hasChanges) => { + if (!hasChanges) { + this.initializeOriginalFields(); + } else { + this.checkLastModified(); + } + }); + this.updates$ = this.getRelationships().pipe( + relationsToItems(this.item.id, this.itemDataService), + switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items)) + ); + this.initRelationshipObservables(); + } + + initRelationshipObservables() { + const relationships$ = this.getRelationships(); + + const relationshipTypes$ = relationships$.pipe( + flatMap((rels: Relationship[]) => + observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe( + map(([...arr]: Array>) => arr.map((d: RemoteData) => d.payload).filter((type) => hasValue(type))) + ) + ), + distinctUntilChanged(compareArraysUsingIds()) + ); + + this.resolvedRelsAndTypes$ = observableCombineLatest( + relationships$, + relationshipTypes$ + ); + this.relationLabels$ = relationshipTypes$.pipe( + map((types: RelationshipType[]) => Array.from(new Set(types.map((type) => type.leftLabel)))) + ); + } + + /** + * Prevent unnecessary rerendering so fields don't lose focus + */ + trackUpdate(index, update: FieldUpdate) { + return update && update.field ? update.field.uuid : undefined; + } + + /** + * Checks whether or not there are currently updates for this item + */ + hasChanges(): Observable { + return this.objectUpdatesService.hasUpdates(this.url); + } + + /** + * Checks whether or not the item is currently reinstatable + */ + isReinstatable(): Observable { + return this.objectUpdatesService.isReinstatable(this.url); + } + + discard(): void { + const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut }); + this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification); + } + + /** + * Request the object updates service to undo discarding all changes to this item + */ + reinstate() { + this.objectUpdatesService.reinstateFieldUpdates(this.url); + } + + submit(): void { + const updatedItems$ = this.getRelationships().pipe( + first(), + relationsToItems(this.item.id, this.itemDataService), + switchMap((items: Item[]) => this.objectUpdatesService.getUpdatedFields(this.url, items) as Observable) + ); + // TODO: Delete relationships + } + + private initializeOriginalFields() { + this.getRelationships().pipe( + first(), + relationsToItems(this.item.id, this.itemDataService) + ).subscribe((items: Item[]) => { + this.objectUpdatesService.initialize(this.url, items, this.item.lastModified); + }); + } + + /** + * Checks if the current item is still in sync with the version in the store + * If it's not, a notification is shown and the changes are removed + */ + private checkLastModified() { + const currentVersion = this.item.lastModified; + this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( + (updateVersion: Date) => { + if (updateVersion.getDate() !== currentVersion.getDate()) { + this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); + this.initializeOriginalFields(); + } + } + ); + } + + public getRelationships(): Observable { + return this.item.relationships.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + map((rels: PaginatedList) => rels.page), + hasValueOperator(), + distinctUntilChanged(compareArraysUsingIds()) + ); + } + + public getRelatedItemsByLabel(label: string): Observable { + return this.resolvedRelsAndTypes$.pipe( + filterRelationsByTypeLabel(label), + relationsToItems(this.item.id, this.itemDataService) + ); + } + + public getUpdatesByLabel(label: string): Observable { + return this.getRelatedItemsByLabel(label).pipe( + switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items)) + ) + } + + /** + * Get translated notification title + * @param key + */ + private getNotificationTitle(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.title'); + } + + /** + * Get translated notification content + * @param key + */ + private getNotificationContent(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.content'); + + } + +} diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index a13fb9487b..6ef3ca91ca 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -103,6 +103,21 @@ export class ObjectUpdatesService { })) } + getFieldUpdatesExclusive(url: string, initialFields: Identifiable[]): Observable { + const objectUpdates = this.getObjectEntry(url); + return objectUpdates.pipe(map((objectEntry) => { + const fieldUpdates: FieldUpdates = {}; + for (const object of initialFields) { + let fieldUpdate = objectEntry.fieldUpdates[object.uuid]; + if (isEmpty(fieldUpdate)) { + fieldUpdate = { field: object, changeType: undefined }; + } + fieldUpdates[object.uuid] = fieldUpdate; + } + return fieldUpdates; + })) + } + /** * Method to check if a specific field is currently editable in the store * @param url The URL of the page on which the field resides From 4a749cf91de89223b93e301d89bb685f01615c20 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 3 Apr 2019 11:58:45 +0200 Subject: [PATCH 02/60] 61142: AbstractItemUpdate component and refactoring of item-metadata and item-relationships --- resources/i18n/en.json | 25 +++ .../abstract-item-update.component.ts | 174 ++++++++++++++++++ .../item-metadata/item-metadata.component.ts | 152 ++------------- .../item-relationships.component.html | 2 +- .../item-relationships.component.ts | 159 ++++------------ 5 files changed, 254 insertions(+), 258 deletions(-) create mode 100644 src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 8b7d15abfd..b64edd42d5 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -273,6 +273,31 @@ "content": "Your changes to this item's metadata were saved." } } + }, + "relationships": { + "discard-button": "Discard", + "reinstate-button": "Undo", + "save-button": "Save", + "edit": { + "buttons": { + "remove": "Remove", + "undo": "Undo changes" + } + }, + "notifications": { + "outdated": { + "title": "Changed outdated", + "content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts" + }, + "discarded": { + "title": "Changed discarded", + "content": "Your changes were discarded. To reinstate your changes click the 'Undo' button" + }, + "saved": { + "title": "Relationships saved", + "content": "Your changes to this item's relationships were saved." + } + } } } }, diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts new file mode 100644 index 0000000000..3cc2a5ed84 --- /dev/null +++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -0,0 +1,174 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; +import { Observable } from 'rxjs/internal/Observable'; +import { Item } from '../../../core/shared/item.model'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; +import { first, map } from 'rxjs/operators'; +import { RemoteData } from '../../../core/data/remote-data'; + +@Component({ + selector: 'ds-abstract-item-update', + template: ``, +}) +/** + * Abstract component for managing object updates of an item + */ +export abstract class AbstractItemUpdateComponent implements OnInit { + /** + * The item to display the edit page for + */ + protected item: Item; + /** + * The current values and updates for all this item's metadata fields + */ + protected updates$: Observable; + /** + * The current url of this page + */ + protected url: string; + /** + * Prefix for this component's notification translate keys + */ + protected notificationsPrefix; + /** + * The time span for being able to undo discarding changes + */ + protected discardTimeOut: number; + + constructor( + protected itemService: ItemDataService, + protected objectUpdatesService: ObjectUpdatesService, + protected router: Router, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected route: ActivatedRoute + ) { + + } + + /** + * Initialize common properties between item-update components + */ + ngOnInit(): void { + this.route.parent.data.pipe(map((data) => data.item)) + .pipe( + first(), + map((data: RemoteData) => data.payload) + ).subscribe((item: Item) => { + this.item = item; + }); + + this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; + this.url = this.router.url; + if (this.url.indexOf('?') > 0) { + this.url = this.url.substr(0, this.url.indexOf('?')); + } + this.hasChanges().pipe(first()).subscribe((hasChanges) => { + if (!hasChanges) { + this.initializeOriginalFields(); + } else { + this.checkLastModified(); + } + }); + + this.initializeNotificationsPrefix(); + } + + /** + * Initialize the prefix for notification messages + */ + abstract initializeNotificationsPrefix(): void; + + /** + * Sends all initial values of this item to the object updates service + */ + abstract initializeOriginalFields(): void; + + /** + * Prevent unnecessary rerendering so fields don't lose focus + */ + trackUpdate(index, update: FieldUpdate) { + return update && update.field ? update.field.uuid : undefined; + } + + /** + * Checks whether or not there are currently updates for this item + */ + hasChanges(): Observable { + return this.objectUpdatesService.hasUpdates(this.url); + } + + /** + * Check if the current page is entirely valid + */ + protected isValid() { + return this.objectUpdatesService.isValidPage(this.url); + } + + /** + * Checks if the current item is still in sync with the version in the store + * If it's not, a notification is shown and the changes are removed + */ + private checkLastModified() { + const currentVersion = this.item.lastModified; + this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( + (updateVersion: Date) => { + if (updateVersion.getDate() !== currentVersion.getDate()) { + this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); + this.initializeOriginalFields(); + } + } + ); + } + + /** + * Submit the current changes + */ + abstract submit(): void; + + /** + * Request the object updates service to discard all current changes to this item + * Shows a notification to remind the user that they can undo this + */ + discard() { + const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut }); + this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification); + } + + /** + * Request the object updates service to undo discarding all changes to this item + */ + reinstate() { + this.objectUpdatesService.reinstateFieldUpdates(this.url); + } + + /** + * Checks whether or not the item is currently reinstatable + */ + isReinstatable(): Observable { + return this.objectUpdatesService.isReinstatable(this.url); + } + + /** + * Get translated notification title + * @param key + */ + protected getNotificationTitle(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.title'); + } + + /** + * Get translated notification content + * @param key + */ + protected getNotificationContent(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.content'); + + } +} diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index 6b3e05c818..7c9202c3b9 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -6,8 +6,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { cloneDeep } from 'lodash'; import { Observable } from 'rxjs'; import { - FieldUpdate, - FieldUpdates, Identifiable } from '../../../core/data/object-updates/object-updates.reducer'; import { first, map, switchMap, take, tap } from 'rxjs/operators'; @@ -20,6 +18,7 @@ import { RegistryService } from '../../../core/registry/registry.service'; import { MetadataField } from '../../../core/metadata/metadatafield.model'; import { MetadatumViewModel } from '../../../core/shared/metadata.models'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; @Component({ selector: 'ds-item-metadata', @@ -29,28 +28,7 @@ import { Metadata } from '../../../core/shared/metadata.utils'; /** * Component for displaying an item's metadata edit page */ -export class ItemMetadataComponent implements OnInit { - - /** - * The item to display the edit page for - */ - item: Item; - /** - * The current values and updates for all this item's metadata fields - */ - updates$: Observable; - /** - * The current url of this page - */ - url: string; - /** - * The time span for being able to undo discarding changes - */ - private discardTimeOut: number; - /** - * Prefix for this component's notification translate keys - */ - private notificationsPrefix = 'item.edit.metadata.notifications.'; +export class ItemMetadataComponent extends AbstractItemUpdateComponent { /** * Observable with a list of strings with all existing metadata field keys @@ -58,90 +36,54 @@ export class ItemMetadataComponent implements OnInit { metadataFields$: Observable; constructor( - private itemService: ItemDataService, - private objectUpdatesService: ObjectUpdatesService, - private router: Router, - private notificationsService: NotificationsService, - private translateService: TranslateService, + protected itemService: ItemDataService, + protected objectUpdatesService: ObjectUpdatesService, + protected router: Router, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, - private route: ActivatedRoute, - private metadataFieldService: RegistryService, + protected route: ActivatedRoute, + protected metadataFieldService: RegistryService, ) { - + super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } /** * Set up and initialize all fields */ ngOnInit(): void { + super.ngOnInit(); this.metadataFields$ = this.findMetadataFields(); - this.route.parent.data.pipe(map((data) => data.item)) - .pipe( - first(), - map((data: RemoteData) => data.payload) - ).subscribe((item: Item) => { - this.item = item; - }); - - this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; - this.url = this.router.url; - if (this.url.indexOf('?') > 0) { - this.url = this.url.substr(0, this.url.indexOf('?')); - } - this.hasChanges().pipe(first()).subscribe((hasChanges) => { - if (!hasChanges) { - this.initializeOriginalFields(); - } else { - this.checkLastModified(); - } - }); this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); } + /** + * Initialize the prefix for notification messages + */ + public initializeNotificationsPrefix(): void { + this.notificationsPrefix = 'item.edit.metadata.notifications.'; + } + /** * Sends a new add update for a field to the object updates service * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum */ add(metadata: MetadatumViewModel = new MetadatumViewModel()) { this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata); - - } - - /** - * Request the object updates service to discard all current changes to this item - * Shows a notification to remind the user that they can undo this - */ - discard() { - const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut }); - this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification); - } - - /** - * Request the object updates service to undo discarding all changes to this item - */ - reinstate() { - this.objectUpdatesService.reinstateFieldUpdates(this.url); } /** * Sends all initial values of this item to the object updates service */ - private initializeOriginalFields() { + public initializeOriginalFields() { this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified); } - /** - * Prevent unnecessary rerendering so fields don't lose focus - */ - trackUpdate(index, update: FieldUpdate) { - return update && update.field ? update.field.uuid : undefined; - } - /** * Requests all current metadata for this item and requests the item service to update the item * Makes sure the new version of the item is rendered on the page */ - submit() { + public submit() { this.isValid().pipe(first()).subscribe((isValid) => { if (isValid) { const metadata$: Observable = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable; @@ -167,60 +109,6 @@ export class ItemMetadataComponent implements OnInit { }); } - /** - * Checks whether or not there are currently updates for this item - */ - hasChanges(): Observable { - return this.objectUpdatesService.hasUpdates(this.url); - } - - /** - * Checks whether or not the item is currently reinstatable - */ - isReinstatable(): Observable { - return this.objectUpdatesService.isReinstatable(this.url); - } - - /** - * Checks if the current item is still in sync with the version in the store - * If it's not, a notification is shown and the changes are removed - */ - private checkLastModified() { - const currentVersion = this.item.lastModified; - this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( - (updateVersion: Date) => { - if (updateVersion.getDate() !== currentVersion.getDate()) { - this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); - this.initializeOriginalFields(); - } - } - ); - } - - /** - * Check if the current page is entirely valid - */ - private isValid() { - return this.objectUpdatesService.isValidPage(this.url); - } - - /** - * Get translated notification title - * @param key - */ - private getNotificationTitle(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.title'); - } - - /** - * Get translated notification content - * @param key - */ - private getNotificationContent(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.content'); - - } - /** * Method to request all metadata fields and convert them to a list of strings */ diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html index c95812b4bb..cfa3b8f415 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -19,7 +19,7 @@
{{label}}
-
; - /** - * The current url of this page - */ - url: string; - /** - * Prefix for this component's notification translate keys - */ - private notificationsPrefix = 'item.edit.metadata.notifications.'; +/** + * Component for displaying an item's relationships edit page + */ +export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { /** * The labels of all different relations within this item @@ -56,47 +37,20 @@ export class ItemRelationshipsComponent implements OnInit { * Resolved relationships and types together in one observable */ resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>; - /** - * The time span for being able to undo discarding changes - */ - private discardTimeOut: number; - - constructor(private route: ActivatedRoute, - private router: Router, - private translateService: TranslateService, - @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, - private objectUpdatesService: ObjectUpdatesService, - private notificationsService: NotificationsService, - private itemDataService: ItemDataService) { - } ngOnInit(): void { - this.route.parent.data.pipe(map((data) => data.item)) - .pipe( - first(), - map((data: RemoteData) => data.payload) - ).subscribe((item: Item) => { - this.item = item; - }); - this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; - this.url = this.router.url; - if (this.url.indexOf('?') > 0) { - this.url = this.url.substr(0, this.url.indexOf('?')); - } - this.hasChanges().pipe(first()).subscribe((hasChanges) => { - if (!hasChanges) { - this.initializeOriginalFields(); - } else { - this.checkLastModified(); - } - }); + super.ngOnInit(); + this.updates$ = this.getRelationships().pipe( - relationsToItems(this.item.id, this.itemDataService), + relationsToItems(this.item.id, this.itemService), switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items)) ); this.initRelationshipObservables(); } + /** + * Initialize the item's relationship observables for easier access across the component + */ initRelationshipObservables() { const relationships$ = this.getRelationships(); @@ -119,72 +73,36 @@ export class ItemRelationshipsComponent implements OnInit { } /** - * Prevent unnecessary rerendering so fields don't lose focus + * Initialize the prefix for notification messages */ - trackUpdate(index, update: FieldUpdate) { - return update && update.field ? update.field.uuid : undefined; + public initializeNotificationsPrefix(): void { + this.notificationsPrefix = 'item.edit.relationships.notifications.'; } - /** - * Checks whether or not there are currently updates for this item - */ - hasChanges(): Observable { - return this.objectUpdatesService.hasUpdates(this.url); - } - - /** - * Checks whether or not the item is currently reinstatable - */ - isReinstatable(): Observable { - return this.objectUpdatesService.isReinstatable(this.url); - } - - discard(): void { - const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut }); - this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification); - } - - /** - * Request the object updates service to undo discarding all changes to this item - */ - reinstate() { - this.objectUpdatesService.reinstateFieldUpdates(this.url); - } - - submit(): void { + public submit(): void { const updatedItems$ = this.getRelationships().pipe( first(), - relationsToItems(this.item.id, this.itemDataService), + relationsToItems(this.item.id, this.itemService), switchMap((items: Item[]) => this.objectUpdatesService.getUpdatedFields(this.url, items) as Observable) ); // TODO: Delete relationships } - private initializeOriginalFields() { + /** + * Sends all initial values of this item to the object updates service + */ + public initializeOriginalFields() { this.getRelationships().pipe( first(), - relationsToItems(this.item.id, this.itemDataService) + relationsToItems(this.item.id, this.itemService) ).subscribe((items: Item[]) => { this.objectUpdatesService.initialize(this.url, items, this.item.lastModified); }); } /** - * Checks if the current item is still in sync with the version in the store - * If it's not, a notification is shown and the changes are removed + * Fetch all the relationships of the item */ - private checkLastModified() { - const currentVersion = this.item.lastModified; - this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( - (updateVersion: Date) => { - if (updateVersion.getDate() !== currentVersion.getDate()) { - this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); - this.initializeOriginalFields(); - } - } - ); - } - public getRelationships(): Observable { return this.item.relationships.pipe( getSucceededRemoteData(), @@ -195,34 +113,25 @@ export class ItemRelationshipsComponent implements OnInit { ); } + /** + * Transform the item's relationships of a specific type into related items + * @param label The relationship type's label + */ public getRelatedItemsByLabel(label: string): Observable { return this.resolvedRelsAndTypes$.pipe( filterRelationsByTypeLabel(label), - relationsToItems(this.item.id, this.itemDataService) + relationsToItems(this.item.id, this.itemService) ); } + /** + * Get FieldUpdates for the relationships of a specific type + * @param label The relationship type's label + */ public getUpdatesByLabel(label: string): Observable { return this.getRelatedItemsByLabel(label).pipe( switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items)) ) } - /** - * Get translated notification title - * @param key - */ - private getNotificationTitle(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.title'); - } - - /** - * Get translated notification content - * @param key - */ - private getNotificationContent(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.content'); - - } - } From a99fa4d4a26c1143e386cc0791d7ba93d3b1235a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 3 Apr 2019 13:05:43 +0200 Subject: [PATCH 03/60] 61142: Renaming and refactoring item-relationships --- .../abstract-item-update.component.ts | 10 ++++++++- .../edit-item-page/edit-item-page.module.ts | 4 ++-- .../item-metadata/item-metadata.component.ts | 6 +++++ .../edit-relationship.component.html} | 0 .../edit-relationship.component.scss} | 0 .../edit-relationship.component.spec.ts} | 0 .../edit-relationship.component.ts} | 22 ++++++++++++++----- .../item-relationships.component.html | 2 +- .../item-relationships.component.ts | 18 ++++++++++----- 9 files changed, 47 insertions(+), 15 deletions(-) rename src/app/+item-page/edit-item-page/item-relationships/{edit-in-place-relationship/edit-in-place-relationship.component.html => edit-relationship/edit-relationship.component.html} (100%) rename src/app/+item-page/edit-item-page/item-relationships/{edit-in-place-relationship/edit-in-place-relationship.component.scss => edit-relationship/edit-relationship.component.scss} (100%) rename src/app/+item-page/edit-item-page/item-relationships/{edit-in-place-relationship/edit-in-place-relationship.component.spec.ts => edit-relationship/edit-relationship.component.spec.ts} (100%) rename src/app/+item-page/edit-item-page/item-relationships/{edit-in-place-relationship/edit-in-place-relationship.component.ts => edit-relationship/edit-relationship.component.ts} (74%) diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts index 3cc2a5ed84..76e6eb9446 100644 --- a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts +++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -24,7 +24,8 @@ export abstract class AbstractItemUpdateComponent implements OnInit { */ protected item: Item; /** - * The current values and updates for all this item's metadata fields + * The current values and updates for all this item's fields + * Should be initialized in the initializeUpdates method of the child component */ protected updates$: Observable; /** @@ -33,6 +34,7 @@ export abstract class AbstractItemUpdateComponent implements OnInit { protected url: string; /** * Prefix for this component's notification translate keys + * Should be initialized in the initializeNotificationsPrefix method of the child component */ protected notificationsPrefix; /** @@ -78,8 +80,14 @@ export abstract class AbstractItemUpdateComponent implements OnInit { }); this.initializeNotificationsPrefix(); + this.initializeUpdates(); } + /** + * Initialize the values and updates of the current item's fields + */ + abstract initializeUpdates(): void; + /** * Initialize the prefix for notification messages */ diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index 079f065d5f..db7557b43c 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -16,7 +16,7 @@ import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; -import { EditInPlaceRelationshipComponent } from './item-relationships/edit-in-place-relationship/edit-in-place-relationship.component'; +import { EditRelationshipComponent } from './item-relationships/edit-relationship/edit-relationship.component'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -42,7 +42,7 @@ import { EditInPlaceRelationshipComponent } from './item-relationships/edit-in-p ItemRelationshipsComponent, ItemBitstreamsComponent, EditInPlaceFieldComponent, - EditInPlaceRelationshipComponent + EditRelationshipComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index 7c9202c3b9..6e8be0efb6 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -54,6 +54,12 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { ngOnInit(): void { super.ngOnInit(); this.metadataFields$ = this.findMetadataFields(); + } + + /** + * Initialize the values and updates of the current item's metadata fields + */ + public initializeUpdates(): void { this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); } diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html similarity index 100% rename from src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.html rename to src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.scss similarity index 100% rename from src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.scss rename to src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.scss diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts similarity index 100% rename from src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.spec.ts rename to src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts similarity index 74% rename from src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts rename to src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts index bb29f7a889..b7ca15f211 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-in-place-relationship/edit-in-place-relationship.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts @@ -5,16 +5,14 @@ import { Item } from '../../../../core/shared/item.model'; import { VIEW_MODE_ELEMENT } from '../../../simple/related-items/related-items-component'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; -import { Observable } from 'rxjs/internal/Observable'; -import { map } from 'rxjs/operators'; @Component({ // tslint:disable-next-line:component-selector - selector: '[ds-edit-in-place-relationship]', - styleUrls: ['./edit-in-place-relationship.component.scss'], - templateUrl: './edit-in-place-relationship.component.html', + selector: '[ds-edit-relationship]', + styleUrls: ['./edit-relationship.component.scss'], + templateUrl: './edit-relationship.component.html', }) -export class EditInPlaceRelationshipComponent implements OnChanges { +export class EditRelationshipComponent implements OnChanges { /** * The current field, value and state of the relationship */ @@ -45,18 +43,30 @@ export class EditInPlaceRelationshipComponent implements OnChanges { this.item = cloneDeep(this.fieldUpdate.field) as Item; } + /** + * Sends a new remove update for this field to the object updates service + */ remove(): void { this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.item); } + /** + * Cancels the current update for this field in the object updates service + */ undo(): void { this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.item.uuid); } + /** + * Check if a user should be allowed to remove this field + */ canRemove(): boolean { return this.fieldUpdate.changeType !== FieldChangeType.REMOVE; } + /** + * Check if a user should be allowed to cancel the update to this field + */ canUndo(): boolean { return this.fieldUpdate.changeType >= 0; } diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html index cfa3b8f415..6d7f82a3da 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -20,7 +20,7 @@
{{label}}
-
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html index 6d7f82a3da..50b64fed16 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -17,17 +17,34 @@  {{"item.edit.metadata.save-button" | translate}}
-
-
{{label}}
+
+
{{getRelationshipMessageKey(label) | translate}}
+ [ngClass]="{'alert alert-danger': updateValue.changeType === 2}"> +
+
+
+
+ + +
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss index 898533a9f0..cbedd42280 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss @@ -20,3 +20,14 @@ } + +.relationship-row:not(.alert-danger) { + padding: $alert-padding-y 0; +} + +.relationship-row.alert-danger { + margin-left: -$alert-padding-x; + margin-right: -$alert-padding-x; + margin-top: -1px; + margin-bottom: -1px; +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index adcf0abcad..56bac6c478 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -1,8 +1,8 @@ import { Component, Inject } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; -import { FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; +import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Observable } from 'rxjs/internal/Observable'; -import { switchMap, take } from 'rxjs/operators'; +import { distinctUntilChanged, switchMap, take } from 'rxjs/operators'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { ItemDataService } from '../../../core/data/item-data.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; @@ -98,4 +98,16 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { ) } + /** + * Get the i18n message key for a relationship + * @param label The relationship type's label + */ + public getRelationshipMessageKey(label: string): string { + if (label.indexOf('Of') > -1) { + return `relationships.${label.substring(0, label.indexOf('Of') + 2)}` + } else { + return label; + } + } + } From 1e31fadb70e016008927cbb1f9c473870f88c6dd Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 4 Apr 2019 12:39:25 +0200 Subject: [PATCH 06/60] 61142: Submit intermediate commit --- resources/i18n/en.json | 1 + .../item-relationships.component.html | 23 +++++++----- .../item-relationships.component.ts | 35 ++++++++++++++++--- src/app/core/data/relationship.service.ts | 13 +++---- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index b64edd42d5..284fab6c82 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -733,6 +733,7 @@ "sub-communities": "Loading sub-communities...", "recent-submissions": "Loading recent submissions...", "item": "Loading item...", + "items": "Loading items...", "objects": "Loading...", "search-results": "Loading search results...", "browse-by": "Loading items...", diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html index 50b64fed16..be400649c4 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -18,14 +18,21 @@
-
{{getRelationshipMessageKey(label) | translate}}
-
-
+ +
+
{{getRelationshipMessageKey(label) | translate}}
+ +
+
+ +
+
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 56bac6c478..963db2a67e 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -2,7 +2,8 @@ import { Component, Inject } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Observable } from 'rxjs/internal/Observable'; -import { distinctUntilChanged, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { ItemDataService } from '../../../core/data/item-data.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; @@ -11,6 +12,10 @@ import { NotificationsService } from '../../../shared/notifications/notification import { TranslateService } from '@ngx-translate/core'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { RelationshipService } from '../../../core/data/relationship.service'; +import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; +import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; +import { RestResponse } from '../../../core/cache/response.models'; +import { isNotEmptyOperator } from '../../../shared/empty.util'; @Component({ selector: 'ds-item-relationships', @@ -65,10 +70,32 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { } public submit(): void { - const updatedItems$ = this.relationshipService.getRelatedItems(this.item).pipe( - switchMap((items: Item[]) => this.objectUpdatesService.getUpdatedFields(this.url, items) as Observable) + const removedItemIds$ = this.relationshipService.getRelatedItems(this.item).pipe( + switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items) as Observable), + map((fieldUpdates: FieldUpdates) => Object.values(fieldUpdates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)), + map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field.uuid) as string[]), + isNotEmptyOperator() ); - // TODO: Delete relationships + const allRelationshipsAndRemovedItemIds$ = observableCombineLatest( + this.relationshipService.getItemRelationshipsArray(this.item), + removedItemIds$ + ); + const removedRelationshipIds$ = allRelationshipsAndRemovedItemIds$.pipe( + map(([relationships, itemIds]) => + relationships + .filter((relationship: Relationship) => itemIds.indexOf(relationship.leftId) > -1 || itemIds.indexOf(relationship.rightId) > -1) + .map((relationship: Relationship) => relationship.id)) + ); + removedRelationshipIds$.pipe( + take(1), + switchMap((removedIds: string[]) => observableZip(removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))), + map((responses: RestResponse[]) => responses.filter((response: RestResponse) => response.isSuccessful)) + ).subscribe((responses: RestResponse[]) => { + console.log(responses); + this.initializeOriginalFields(); + this.initializeUpdates(); + this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); + }); } /** diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 6e30696325..c6b8e8319c 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -3,7 +3,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { hasValue, hasValueOperator, isNotEmptyOperator } from '../../shared/empty.util'; -import { distinctUntilChanged, flatMap, map, take } from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, @@ -46,17 +46,12 @@ export class RelationshipService { } deleteRelationship(uuid: string): Observable { - const requestUuid = this.requestService.generateRequestId(); - - this.getRelationshipEndpoint(uuid).pipe( + return this.getRelationshipEndpoint(uuid).pipe( isNotEmptyOperator(), distinctUntilChanged(), - map((endpointURL: string) => new DeleteRequest(requestUuid, endpointURL)), + map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), - take(1) - ).subscribe(); - - return this.requestService.getByUUID(requestUuid).pipe( + switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)), filterSuccessfulResponses() ); } From bb683734894bfd6f79e57fd3bedaa966cb008f8a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 4 Apr 2019 15:19:28 +0200 Subject: [PATCH 07/60] 61142: Working submit (except reloading lists) and JSDocs --- .../item-relationships.component.ts | 12 +++++-- .../object-updates/object-updates.service.ts | 6 ++++ src/app/core/data/relationship.service.ts | 32 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 963db2a67e..90bee12187 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -69,7 +69,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { this.notificationsPrefix = 'item.edit.relationships.notifications.'; } + /** + * Resolve the currently selected related items back to relationships and send a delete request + * Make sure the lists are refreshed afterwards + */ public submit(): void { + // Get all IDs of related items of which their relationship with the current item is about to be removed const removedItemIds$ = this.relationshipService.getRelatedItems(this.item).pipe( switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items) as Observable), map((fieldUpdates: FieldUpdates) => Object.values(fieldUpdates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)), @@ -80,18 +85,21 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { this.relationshipService.getItemRelationshipsArray(this.item), removedItemIds$ ); + // Get all IDs of the relationships that should be removed const removedRelationshipIds$ = allRelationshipsAndRemovedItemIds$.pipe( map(([relationships, itemIds]) => relationships .filter((relationship: Relationship) => itemIds.indexOf(relationship.leftId) > -1 || itemIds.indexOf(relationship.rightId) > -1) .map((relationship: Relationship) => relationship.id)) ); + // Request a delete for every relationship found in the observable created above removedRelationshipIds$.pipe( take(1), - switchMap((removedIds: string[]) => observableZip(removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))), + switchMap((removedIds: string[]) => observableZip(...removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))), map((responses: RestResponse[]) => responses.filter((response: RestResponse) => response.isSuccessful)) ).subscribe((responses: RestResponse[]) => { - console.log(responses); + // Make sure the lists are up-to-date and send a notification that the removal was successful + // TODO: Fix lists refreshing correctly this.initializeOriginalFields(); this.initializeUpdates(); this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index 6ef3ca91ca..c5c44fe36c 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -103,6 +103,12 @@ export class ObjectUpdatesService { })) } + /** + * Method that combines the state's updates (excluding updates that aren't part of the initialFields) with + * the initial values (when there's no update) to create a FieldUpdates object + * @param url The URL of the page for which the FieldUpdates should be requested + * @param initialFields The initial values of the fields + */ getFieldUpdatesExclusive(url: string, initialFields: Identifiable[]): Observable { const objectUpdates = this.getObjectEntry(url); return objectUpdates.pipe(map((objectEntry) => { diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index c6b8e8319c..d0308dead7 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -39,12 +39,20 @@ export class RelationshipService { protected itemService: ItemDataService) { } + /** + * Get the endpoint for a relationship by ID + * @param uuid + */ getRelationshipEndpoint(uuid: string) { return this.halService.getEndpoint(this.linkPath).pipe( map((href: string) => `${href}/${uuid}`) ); } + /** + * Send a delete request for a relationship by ID + * @param uuid + */ deleteRelationship(uuid: string): Observable { return this.getRelationshipEndpoint(uuid).pipe( isNotEmptyOperator(), @@ -56,6 +64,11 @@ export class RelationshipService { ); } + /** + * Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types + * This is used for easier access of a relationship's type because they exist as observables + * @param item + */ getItemResolvedRelsAndTypes(item: Item): Observable<[Relationship[], RelationshipType[]]> { const relationships$ = this.getItemRelationshipsArray(item); @@ -74,6 +87,10 @@ export class RelationshipService { ); } + /** + * Get an item their relationships in the form of an array + * @param item + */ getItemRelationshipsArray(item: Item): Observable { return item.relationships.pipe( getSucceededRemoteData(), @@ -84,6 +101,11 @@ export class RelationshipService { ); } + /** + * Get an array of an item their unique relationship type's labels + * The array doesn't contain any duplicate labels + * @param item + */ getItemRelationshipLabels(item: Item): Observable { return this.getItemResolvedRelsAndTypes(item).pipe( map(([relsCurrentPage, relTypesCurrentPage]) => { @@ -100,12 +122,22 @@ export class RelationshipService { ) } + /** + * Resolve a given item's relationships into related items and return the items as an array + * @param item + */ getRelatedItems(item: Item): Observable { return this.getItemRelationshipsArray(item).pipe( relationsToItems(item.uuid, this.itemService) ); } + /** + * Resolve a given item's relationships into related items, filtered by a relationship label + * and return the items as an array + * @param item + * @param label + */ getRelatedItemsByLabel(item: Item, label: string): Observable { return this.getItemResolvedRelsAndTypes(item).pipe( filterRelationsByTypeLabel(label), From 0cf4cdc1c5a80d249792fa06c53d99d88640f7dd Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 4 Apr 2019 17:43:40 +0200 Subject: [PATCH 08/60] 61142: ItemRelationshipComponent test + item de-caching --- .../item-metadata.component.spec.ts | 2 +- .../item-relationships.component.spec.ts | 234 ++++++++++++++++++ .../item-relationships.component.ts | 8 +- 3 files changed, 242 insertions(+), 2 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts index f2cd74fc2f..2b50b75a2a 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.spec.ts @@ -28,7 +28,7 @@ import { MetadataSchema } from '../../../core/metadata/metadataschema.model'; import { MetadataField } from '../../../core/metadata/metadatafield.model'; import { Metadata } from '../../../core/shared/metadata.utils'; -let comp: ItemMetadataComponent; +let comp: any; let fixture: ComponentFixture; let de: DebugElement; let el: HTMLElement; diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts index e69de29bb2..8579d25fdf 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts @@ -0,0 +1,234 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ItemRelationshipsComponent } from './item-relationships.component'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { INotification, Notification } from '../../../shared/notifications/models/notification.model'; +import { NotificationType } from '../../../shared/notifications/models/notification-type'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { TestScheduler } from 'rxjs/testing'; +import { SharedModule } from '../../../shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { GLOBAL_CONFIG } from '../../../../config'; +import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; +import { ResourceType } from '../../../core/shared/resource-type'; +import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; +import { of as observableOf } from 'rxjs'; +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'; +import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; +import { RelationshipService } from '../../../core/data/relationship.service'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { getTestScheduler } from 'jasmine-marbles'; +import { By } from '@angular/platform-browser'; +import { RestResponse } from '../../../core/cache/response.models'; + +let comp: any; +let fixture: ComponentFixture; +let de: DebugElement; +let el: HTMLElement; +let objectUpdatesService; +let relationshipService; +let objectCache; +const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); +const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); +const successNotification: INotification = new Notification('id', NotificationType.Success, 'success'); +const notificationsService = jasmine.createSpyObj('notificationsService', + { + info: infoNotification, + warning: warningNotification, + success: successNotification + } +); +const router = new RouterStub(); +let routeStub; +let itemService; + +const url = 'http://test-url.com/test-url'; +router.url = url; + +let scheduler: TestScheduler; +let item; +let author1; +let author2; +let fieldUpdate1; +let fieldUpdate2; +let relationships; +let relationshipType; + +describe('ItemRelationshipsComponent', () => { + beforeEach(async(() => { + const date = new Date(); + + relationshipType = Object.assign(new RelationshipType(), { + type: ResourceType.RelationshipType, + id: '1', + uuid: '1', + leftLabel: 'isAuthorOfPublication', + rightLabel: 'isPublicationOfAuthor' + }); + + relationships = [ + Object.assign(new Relationship(), { + self: url + '/2', + id: '2', + uuid: '2', + leftId: 'author1', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }), + Object.assign(new Relationship(), { + self: url + '/3', + id: '3', + uuid: '3', + leftId: 'author2', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }) + ]; + + item = Object.assign(new Item(), { + self: 'fake-item-url/publication', + id: 'publication', + uuid: 'publication', + relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships))), + lastModified: date + }); + + author1 = Object.assign(new Item(), { + id: 'author1', + uuid: 'author1' + }); + author2 = Object.assign(new Item(), { + id: 'author2', + uuid: 'author2' + }); + + fieldUpdate1 = { + field: author1, + changeType: undefined + }; + fieldUpdate2 = { + field: author2, + changeType: FieldChangeType.REMOVE + }; + + itemService = jasmine.createSpyObj('itemService', { + findById: observableOf(new RemoteData(false, false, true, undefined, item)) + }); + routeStub = { + parent: { + data: observableOf({ item: new RemoteData(false, false, true, null, item) }) + } + }; + + objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', + { + getFieldUpdates: observableOf({ + [author1.uuid]: fieldUpdate1, + [author2.uuid]: fieldUpdate2 + }), + getFieldUpdatesExclusive: observableOf({ + [author1.uuid]: fieldUpdate1, + [author2.uuid]: fieldUpdate2 + }), + saveAddFieldUpdate: {}, + discardFieldUpdates: {}, + reinstateFieldUpdates: observableOf(true), + initialize: {}, + getUpdatedFields: observableOf([author1, author2]), + getLastModified: observableOf(date), + hasUpdates: observableOf(true), + isReinstatable: observableOf(false), // should always return something --> its in ngOnInit + isValidPage: observableOf(true) + } + ); + + relationshipService = jasmine.createSpyObj('relationshipService', + { + getItemRelationshipLabels: observableOf(['isAuthorOfPublication']), + getRelatedItems: observableOf([author1, author2]), + getRelatedItemsByLabel: observableOf([author1, author2]), + getItemRelationshipsArray: observableOf(relationships), + deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')) + } + ); + + objectCache = jasmine.createSpyObj('objectCache', { + remove: undefined + }); + + scheduler = getTestScheduler(); + TestBed.configureTestingModule({ + imports: [SharedModule, TranslateModule.forRoot()], + declarations: [ItemRelationshipsComponent], + providers: [ + { provide: ItemDataService, useValue: itemService }, + { provide: ObjectUpdatesService, useValue: objectUpdatesService }, + { provide: Router, useValue: router }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: GLOBAL_CONFIG, useValue: { item: { edit: { undoTimeout: 10 } } } as any }, + { provide: RelationshipService, useValue: relationshipService }, + { provide: ObjectCacheService, useValue: objectCache } + ], schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemRelationshipsComponent); + comp = fixture.componentInstance; + de = fixture.debugElement; + el = de.nativeElement; + comp.url = url; + fixture.detectChanges(); + }); + + describe('discard', () => { + beforeEach(() => { + comp.discard(); + }); + + it('it should call discardFieldUpdates on the objectUpdatesService with the correct url and notification', () => { + expect(objectUpdatesService.discardFieldUpdates).toHaveBeenCalledWith(url, infoNotification); + }); + }); + + describe('reinstate', () => { + beforeEach(() => { + comp.reinstate(); + }); + + it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct url', () => { + expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(url); + }); + }); + + describe('changeType is REMOVE', () => { + 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'); + }); + }); + + describe('submit', () => { + beforeEach(() => { + comp.submit(); + }); + + it('it should delete the correct relationship and de-cache the current item', () => { + expect(relationshipService.deleteRelationship).toHaveBeenCalledWith(relationships[1].uuid); + expect(objectCache.remove).toHaveBeenCalledWith(item.self); + }); + }); +}); diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 90bee12187..bcf43bf364 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -16,6 +16,9 @@ import { FieldChangeType } from '../../../core/data/object-updates/object-update import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { RestResponse } from '../../../core/cache/response.models'; import { isNotEmptyOperator } from '../../../shared/empty.util'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; @Component({ selector: 'ds-item-relationships', @@ -40,7 +43,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { protected translateService: TranslateService, @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, protected route: ActivatedRoute, - protected relationshipService: RelationshipService + protected relationshipService: RelationshipService, + protected objectCache: ObjectCacheService ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } @@ -100,6 +104,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { ).subscribe((responses: RestResponse[]) => { // Make sure the lists are up-to-date and send a notification that the removal was successful // TODO: Fix lists refreshing correctly + this.objectCache.remove(this.item.self); + this.itemService.findById(this.item.id).pipe(getSucceededRemoteData(), take(1)).subscribe((itemRD: RemoteData) => this.item = itemRD.payload); this.initializeOriginalFields(); this.initializeUpdates(); this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); From b78b2e5b8227ebd828bc6861f99c04e6efec6b71 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 5 Apr 2019 13:46:48 +0200 Subject: [PATCH 09/60] 61142: EditRelationshipComponent tests + de-caching of requests in ItemRelationships --- .../edit-relationship.component.spec.ts | 180 ++++++++++++++++++ .../item-relationships.component.ts | 5 +- src/app/core/cache/object-cache.service.ts | 4 +- 3 files changed, 186 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts index e69de29bb2..fc6c999a1c 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts @@ -0,0 +1,180 @@ +import { async, TestBed } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { TranslateModule } from '@ngx-translate/core'; +import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { EditRelationshipComponent } from './edit-relationship.component'; +import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; +import { ResourceType } from '../../../../core/shared/resource-type'; +import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; +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'; +import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; + +let objectUpdatesService: ObjectUpdatesService; +const url = 'http://test-url.com/test-url'; + +let item; +let author1; +let author2; +let fieldUpdate1; +let fieldUpdate2; +let relationships; +let relationshipType; + +let fixture; +let comp: EditRelationshipComponent; +let de; +let el; + +describe('EditRelationshipComponent', () => { + beforeEach(async(() => { + relationshipType = Object.assign(new RelationshipType(), { + type: ResourceType.RelationshipType, + id: '1', + uuid: '1', + leftLabel: 'isAuthorOfPublication', + rightLabel: 'isPublicationOfAuthor' + }); + + relationships = [ + Object.assign(new Relationship(), { + self: url + '/2', + id: '2', + uuid: '2', + leftId: 'author1', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }), + Object.assign(new Relationship(), { + self: url + '/3', + id: '3', + uuid: '3', + leftId: 'author2', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }) + ]; + + item = Object.assign(new Item(), { + self: 'fake-item-url/publication', + id: 'publication', + uuid: 'publication', + relationships: observableOf(new RemoteData(false, false, true, undefined, 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, + changeType: undefined + }; + fieldUpdate2 = { + field: author2, + changeType: FieldChangeType.REMOVE + }; + + objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', + { + saveChangeFieldUpdate: {}, + saveRemoveFieldUpdate: {}, + setEditableFieldUpdate: {}, + setValidFieldUpdate: {}, + removeSingleFieldUpdate: {}, + isEditable: observableOf(false), // should always return something --> its in ngOnInit + isValid: observableOf(true) // should always return something --> its in ngOnInit + } + ); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [EditRelationshipComponent], + providers: [ + { provide: ObjectUpdatesService, useValue: objectUpdatesService } + ], schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditRelationshipComponent); + comp = fixture.componentInstance; + de = fixture.debugElement; + el = de.nativeElement; + + comp.url = url; + comp.fieldUpdate = fieldUpdate1; + comp.item = item; + + fixture.detectChanges(); + }); + + describe('when fieldUpdate has no changeType', () => { + beforeEach(() => { + comp.fieldUpdate = fieldUpdate1; + fixture.detectChanges(); + }); + + describe('canRemove', () => { + it('should return true', () => { + expect(comp.canRemove()).toBe(true); + }); + }); + + describe('canUndo', () => { + it('should return false', () => { + expect(comp.canUndo()).toBe(false); + }); + }); + }); + + describe('when fieldUpdate has DELETE as changeType', () => { + beforeEach(() => { + comp.fieldUpdate = fieldUpdate2; + fixture.detectChanges(); + }); + + describe('canRemove', () => { + it('should return false', () => { + expect(comp.canRemove()).toBe(false); + }); + }); + + describe('canUndo', () => { + it('should return true', () => { + expect(comp.canUndo()).toBe(true); + }); + }); + }); + + describe('remove', () => { + beforeEach(() => { + comp.remove(); + }); + + it('should call saveRemoveFieldUpdate with the correct arguments', () => { + expect(objectUpdatesService.saveRemoveFieldUpdate).toHaveBeenCalledWith(url, item); + }); + }); + + describe('undo', () => { + beforeEach(() => { + comp.undo(); + }); + + it('should call removeSingleFieldUpdate with the correct arguments', () => { + expect(objectUpdatesService.removeSingleFieldUpdate).toHaveBeenCalledWith(url, item.uuid); + }); + }); + +}); diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index bcf43bf364..9d348c6e13 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -19,6 +19,7 @@ import { isNotEmptyOperator } from '../../../shared/empty.util'; import { RemoteData } from '../../../core/data/remote-data'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { getSucceededRemoteData } from '../../../core/shared/operators'; +import { RequestService } from '../../../core/data/request.service'; @Component({ selector: 'ds-item-relationships', @@ -44,7 +45,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, protected route: ActivatedRoute, protected relationshipService: RelationshipService, - protected objectCache: ObjectCacheService + protected objectCache: ObjectCacheService, + protected requestService: RequestService ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } @@ -105,6 +107,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { // Make sure the lists are up-to-date and send a notification that the removal was successful // TODO: Fix lists refreshing correctly this.objectCache.remove(this.item.self); + this.requestService.removeByHrefSubstring(this.item.self); this.itemService.findById(this.item.id).pipe(getSucceededRemoteData(), take(1)).subscribe((itemRD: RemoteData) => this.item = itemRD.payload); this.initializeOriginalFields(); this.initializeUpdates(); diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index 483de65b98..d415da50b3 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -68,8 +68,8 @@ export class ObjectCacheService { * @param href * The unique href of the object to be removed */ - remove(uuid: string): void { - this.store.dispatch(new RemoveFromObjectCacheAction(uuid)); + remove(href: string): void { + this.store.dispatch(new RemoveFromObjectCacheAction(href)); } /** From 9f27a89dc4398b80303cd4ffc5d4f4ba0974aae8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 5 Apr 2019 15:21:10 +0200 Subject: [PATCH 10/60] 61142: RelationshipService tests --- .../core/data/relationship.service.spec.ts | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/app/core/data/relationship.service.spec.ts diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts new file mode 100644 index 0000000000..0e417b7ffe --- /dev/null +++ b/src/app/core/data/relationship.service.spec.ts @@ -0,0 +1,137 @@ +import { RelationshipService } from './relationship.service'; +import { RequestService } from './request.service'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; +import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { RequestEntry } from './request.reducer'; +import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; +import { ResourceType } from '../shared/resource-type'; +import { Relationship } from '../shared/item-relationships/relationship.model'; +import { RemoteData } from './remote-data'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { Item } from '../shared/item.model'; +import { PaginatedList } from './paginated-list'; +import { PageInfo } from '../shared/page-info.model'; +import { DeleteRequest } from './request.models'; + +describe('RelationshipService', () => { + let service: RelationshipService; + let requestService: RequestService; + + const restEndpointURL = 'https://rest.api/'; + const relationshipsEndpointURL = `${restEndpointURL}/relationships`; + const halService: any = new HALEndpointServiceStub(restEndpointURL); + const rdbService = getMockRemoteDataBuildService(); + + const relationshipType = Object.assign(new RelationshipType(), { + type: ResourceType.RelationshipType, + id: '1', + uuid: '1', + leftLabel: 'isAuthorOfPublication', + rightLabel: 'isPublicationOfAuthor' + }); + + const relationships = [ + Object.assign(new Relationship(), { + self: relationshipsEndpointURL + '/2', + id: '2', + uuid: '2', + leftId: 'author1', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }), + Object.assign(new Relationship(), { + self: relationshipsEndpointURL + '/3', + id: '3', + uuid: '3', + leftId: 'author2', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }) + ]; + + const item = Object.assign(new Item(), { + self: 'fake-item-url/publication', + id: 'publication', + uuid: 'publication', + relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships))) + }); + + const relatedItem1 = Object.assign(new Item(), { + id: 'author1', + uuid: 'author1' + }); + const relatedItem2 = Object.assign(new Item(), { + id: 'author2', + uuid: 'author2' + }); + const relatedItems = [relatedItem1, relatedItem2]; + + const itemService = jasmine.createSpyObj('itemService', { + findById: (uuid) => new RemoteData(false, false, true, undefined, relatedItems.filter((item) => item.id === uuid)[0]) + }); + + function initTestService() { + return new RelationshipService( + requestService, + halService, + rdbService, + itemService + ); + } + + const getRequestEntry$ = (successful: boolean) => { + return observableOf({ + response: { isSuccessful: successful, payload: relationships } as any + } as RequestEntry) + }; + + beforeEach(() => { + requestService = getMockRequestService(getRequestEntry$(true)); + service = initTestService(); + }); + + describe('deleteRelationship', () => { + beforeEach(() => { + service.deleteRelationship(relationships[0].uuid).subscribe(); + }); + + it('should send a DeleteRequest', () => { + const expected = new DeleteRequest(requestService.generateRequestId(), relationshipsEndpointURL + '/' + relationships[0].uuid); + expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + }); + }); + + describe('getItemRelationshipsArray', () => { + it('should return the item\'s relationships in the form of an array', () => { + service.getItemRelationshipsArray(item).subscribe((result) => { + expect(result).toEqual(relationships); + }); + }); + }); + + describe('getItemRelationshipLabels', () => { + it('should return the correct labels', () => { + service.getItemRelationshipLabels(item).subscribe((result) => { + expect(result).toEqual([relationshipType.rightLabel]); + }); + }); + }); + + describe('getRelatedItems', () => { + it('should return the related items', () => { + service.getRelatedItems(item).subscribe((result) => { + expect(result).toEqual(relatedItems); + }); + }); + }); + + describe('getRelatedItemsByLabel', () => { + it('should return the related items by label', () => { + service.getRelatedItemsByLabel(item, relationshipType.rightLabel).subscribe((result) => { + expect(result).toEqual(relatedItems); + }); + }); + }) + +}); From 01b60dbf3459a6542bdfeb36ad5118d135117f36 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 5 Apr 2019 17:28:20 +0200 Subject: [PATCH 11/60] 61142: RemoveByHrefSubstring fix --- .../item-relationships/item-relationships.component.ts | 2 +- src/app/core/data/request.service.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 9d348c6e13..8e8a31f896 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -108,7 +108,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { // TODO: Fix lists refreshing correctly this.objectCache.remove(this.item.self); this.requestService.removeByHrefSubstring(this.item.self); - this.itemService.findById(this.item.id).pipe(getSucceededRemoteData(), take(1)).subscribe((itemRD: RemoteData) => this.item = itemRD.payload); + // this.itemService.findById(this.item.id).pipe(getSucceededRemoteData(), take(1)).subscribe((itemRD: RemoteData) => this.item = itemRD.payload); this.initializeOriginalFields(); this.initializeUpdates(); this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index fd463047f1..82c2a47491 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { Observable, race as observableRace } from 'rxjs'; -import { filter, mergeMap, take } from 'rxjs/operators'; +import { filter, map, mergeMap, take, tap } from 'rxjs/operators'; import { AppState } from '../../app.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -64,8 +64,7 @@ const uuidsFromHrefSubstringSelector = const getUuidsFromHrefSubstring = (state: IndexState, href: string): string[] => { let result = []; if (isNotEmpty(state)) { - result = Object.values(state) - .filter((value: string) => value.startsWith(href)); + result = Object.keys(state).filter((key) => key.startsWith(href)).map((key) => state[key]); } return result; }; From bbbd6959a8b01f02707cbb2520a3b649e0e4d7ec Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Apr 2019 15:28:18 +0200 Subject: [PATCH 12/60] 61142: EditRelationshipList component to proper reload relationships and fix performance issues --- .../edit-item-page/edit-item-page.module.ts | 4 +- .../edit-relationship-list.component.html | 15 ++ .../edit-relationship-list.component.scss | 12 ++ .../edit-relationship-list.component.spec.ts | 137 ++++++++++++++++++ .../edit-relationship-list.component.ts | 99 +++++++++++++ .../item-relationships.component.html | 16 +- .../item-relationships.component.scss | 11 -- .../item-relationships.component.spec.ts | 28 ++-- .../item-relationships.component.ts | 60 ++++---- src/app/core/cache/object-cache.service.ts | 14 +- src/app/core/data/request.service.ts | 11 ++ 11 files changed, 332 insertions(+), 75 deletions(-) create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.scss create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index db7557b43c..1542d12ce5 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -17,6 +17,7 @@ import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/e import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; import { EditRelationshipComponent } from './item-relationships/edit-relationship/edit-relationship.component'; +import { EditRelationshipListComponent } from './item-relationships/edit-relationship-list/edit-relationship-list.component'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -42,7 +43,8 @@ import { EditRelationshipComponent } from './item-relationships/edit-relationshi ItemRelationshipsComponent, ItemBitstreamsComponent, EditInPlaceFieldComponent, - EditRelationshipComponent + EditRelationshipComponent, + EditRelationshipListComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html new file mode 100644 index 0000000000..ba5164e81a --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html @@ -0,0 +1,15 @@ + +
+
{{getRelationshipMessageKey(relationshipLabel) | translate}}
+ +
+
+ +
+
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.scss b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.scss new file mode 100644 index 0000000000..ec6cd2ba78 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.scss @@ -0,0 +1,12 @@ +@import '../../../../../styles/variables.scss'; + +.relationship-row:not(.alert-danger) { + padding: $alert-padding-y 0; +} + +.relationship-row.alert-danger { + margin-left: -$alert-padding-x; + margin-right: -$alert-padding-x; + margin-top: -1px; + margin-bottom: -1px; +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts new file mode 100644 index 0000000000..bd5f5f2e5c --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -0,0 +1,137 @@ +import { EditRelationshipListComponent } from './edit-relationship-list.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; +import { ResourceType } from '../../../../core/shared/resource-type'; +import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +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'; +import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; +import { SharedModule } from '../../../../shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; +import { RelationshipService } from '../../../../core/data/relationship.service'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +let comp: EditRelationshipListComponent; +let fixture: ComponentFixture; +let de: DebugElement; + +let objectUpdatesService; +let relationshipService; + +const url = 'http://test-url.com/test-url'; + +let item; +let author1; +let author2; +let fieldUpdate1; +let fieldUpdate2; +let relationships; +let relationshipType; + +describe('EditRelationshipListComponent', () => { + beforeEach(async(() => { + relationshipType = Object.assign(new RelationshipType(), { + type: ResourceType.RelationshipType, + id: '1', + uuid: '1', + leftLabel: 'isAuthorOfPublication', + rightLabel: 'isPublicationOfAuthor' + }); + + relationships = [ + Object.assign(new Relationship(), { + self: url + '/2', + id: '2', + uuid: '2', + leftId: 'author1', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }), + Object.assign(new Relationship(), { + self: url + '/3', + id: '3', + uuid: '3', + leftId: 'author2', + rightId: 'publication', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }) + ]; + + item = Object.assign(new Item(), { + self: 'fake-item-url/publication', + id: 'publication', + uuid: 'publication', + relationships: observableOf(new RemoteData(false, false, true, undefined, 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, + changeType: undefined + }; + fieldUpdate2 = { + field: author2, + changeType: FieldChangeType.REMOVE + }; + + objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', + { + getFieldUpdatesExclusive: observableOf({ + [author1.uuid]: fieldUpdate1, + [author2.uuid]: fieldUpdate2 + }) + } + ); + + relationshipService = jasmine.createSpyObj('relationshipService', + { + getRelatedItemsByLabel: observableOf([author1, author2]), + } + ); + + TestBed.configureTestingModule({ + imports: [SharedModule, TranslateModule.forRoot()], + declarations: [EditRelationshipListComponent], + providers: [ + { provide: ObjectUpdatesService, useValue: objectUpdatesService }, + { provide: RelationshipService, useValue: relationshipService } + ], schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditRelationshipListComponent); + comp = fixture.componentInstance; + de = fixture.debugElement; + comp.item = item; + comp.url = url; + comp.relationshipLabel = relationshipType.leftLabel; + fixture.detectChanges(); + }); + + describe('changeType is REMOVE', () => { + 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'); + }); + }); +}); diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts new file mode 100644 index 0000000000..765d6484d4 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts @@ -0,0 +1,99 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +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 { RelationshipService } from '../../../../core/data/relationship.service'; +import { Item } from '../../../../core/shared/item.model'; +import { switchMap } from 'rxjs/operators'; +import { hasValue } from '../../../../shared/empty.util'; + +@Component({ + selector: 'ds-edit-relationship-list', + styleUrls: ['./edit-relationship-list.component.scss'], + templateUrl: './edit-relationship-list.component.html', +}) +/** + * A component creating a list of editable relationships of a certain type + * The relationships are rendered as a list of related items + */ +export class EditRelationshipListComponent implements OnInit, OnChanges { + /** + * The item to display related items for + */ + @Input() item: Item; + + /** + * The URL to the current page + * Used to fetch updates for the current item from the store + */ + @Input() url: string; + + /** + * The label of the relationship-type we're rendering a list for + */ + @Input() relationshipLabel: string; + + /** + * The FieldUpdates for the relationships in question + */ + updates$: Observable; + + constructor( + protected objectUpdatesService: ObjectUpdatesService, + protected relationshipService: RelationshipService + ) { + } + + ngOnInit(): void { + this.initUpdates(); + } + + ngOnChanges(changes: SimpleChanges): void { + this.initUpdates(); + } + + /** + * Initialize the FieldUpdates using the related items + */ + initUpdates() { + this.updates$ = this.getUpdatesByLabel(this.relationshipLabel); + } + + /** + * Transform the item's relationships of a specific type into related items + * @param label The relationship type's label + */ + public getRelatedItemsByLabel(label: string): Observable { + return this.relationshipService.getRelatedItemsByLabel(this.item, label); + } + + /** + * Get FieldUpdates for the relationships of a specific type + * @param label The relationship type's label + */ + public getUpdatesByLabel(label: string): Observable { + return this.getRelatedItemsByLabel(label).pipe( + switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items)) + ) + } + + /** + * Get the i18n message key for a relationship + * @param label The relationship type's label + */ + public getRelationshipMessageKey(label: string): string { + if (hasValue(label) && label.indexOf('Of') > -1) { + return `relationships.${label.substring(0, label.indexOf('Of') + 2)}` + } else { + return label; + } + } + + /** + * Prevent unnecessary rerendering so fields don't lose focus + */ + trackUpdate(index, update: FieldUpdate) { + return update && update.field ? update.field.uuid : undefined; + } + +} diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html index be400649c4..4bd0b3df2c 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -18,21 +18,7 @@
- -
-
{{getRelationshipMessageKey(label) | translate}}
- -
-
- -
-
-
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss index cbedd42280..898533a9f0 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.scss @@ -20,14 +20,3 @@ } - -.relationship-row:not(.alert-danger) { - padding: $alert-padding-y 0; -} - -.relationship-row.alert-danger { - margin-left: -$alert-padding-x; - margin-right: -$alert-padding-x; - margin-top: -1px; - margin-bottom: -1px; -} diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts index 8579d25fdf..2439eb4c63 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ItemRelationshipsComponent } from './item-relationships.component'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectorRef, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { INotification, Notification } from '../../../shared/notifications/models/notification.model'; import { NotificationType } from '../../../shared/notifications/models/notification-type'; import { RouterStub } from '../../../shared/testing/router-stub'; @@ -24,8 +24,8 @@ import { FieldChangeType } from '../../../core/data/object-updates/object-update import { RelationshipService } from '../../../core/data/relationship.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { getTestScheduler } from 'jasmine-marbles'; -import { By } from '@angular/platform-browser'; import { RestResponse } from '../../../core/cache/response.models'; +import { RequestService } from '../../../core/data/request.service'; let comp: any; let fixture: ComponentFixture; @@ -33,6 +33,7 @@ let de: DebugElement; let el: HTMLElement; let objectUpdatesService; let relationshipService; +let requestService; let objectCache; const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); @@ -158,6 +159,13 @@ describe('ItemRelationshipsComponent', () => { } ); + requestService = jasmine.createSpyObj('requestService', + { + removeByHrefSubstring: {}, + hasByHrefObservable: observableOf(false) + } + ); + objectCache = jasmine.createSpyObj('objectCache', { remove: undefined }); @@ -174,7 +182,9 @@ describe('ItemRelationshipsComponent', () => { { provide: NotificationsService, useValue: notificationsService }, { provide: GLOBAL_CONFIG, useValue: { item: { edit: { undoTimeout: 10 } } } as any }, { provide: RelationshipService, useValue: relationshipService }, - { provide: ObjectCacheService, useValue: objectCache } + { provide: ObjectCacheService, useValue: objectCache }, + { provide: RequestService, useValue: requestService }, + ChangeDetectorRef ], schemas: [ NO_ERRORS_SCHEMA ] @@ -210,17 +220,6 @@ describe('ItemRelationshipsComponent', () => { }); }); - describe('changeType is REMOVE', () => { - 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'); - }); - }); - describe('submit', () => { beforeEach(() => { comp.submit(); @@ -229,6 +228,7 @@ describe('ItemRelationshipsComponent', () => { it('it should delete the correct relationship and de-cache the current item', () => { expect(relationshipService.deleteRelationship).toHaveBeenCalledWith(relationships[1].uuid); expect(objectCache.remove).toHaveBeenCalledWith(item.self); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self); }); }); }); diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 8e8a31f896..7db8756c78 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -1,8 +1,8 @@ -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Observable } from 'rxjs/internal/Observable'; -import { map, switchMap, take, tap } from 'rxjs/operators'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { ItemDataService } from '../../../core/data/item-data.service'; @@ -20,6 +20,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { getSucceededRemoteData } from '../../../core/shared/operators'; import { RequestService } from '../../../core/data/request.service'; +import { Subscription } from 'rxjs/internal/Subscription'; @Component({ selector: 'ds-item-relationships', @@ -29,13 +30,19 @@ import { RequestService } from '../../../core/data/request.service'; /** * Component for displaying an item's relationships edit page */ -export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { +export class ItemRelationshipsComponent extends AbstractItemUpdateComponent implements OnDestroy { /** * The labels of all different relations within this item */ relationLabels$: Observable; + /** + * 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 + */ + itemUpdateSubscription: Subscription; + constructor( protected itemService: ItemDataService, protected objectUpdatesService: ObjectUpdatesService, @@ -46,7 +53,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { protected route: ActivatedRoute, protected relationshipService: RelationshipService, protected objectCache: ObjectCacheService, - protected requestService: RequestService + protected requestService: RequestService, + protected cdRef: ChangeDetectorRef ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } @@ -57,6 +65,16 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { ngOnInit(): void { super.ngOnInit(); this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item); + + // Update the item (and view) when it's removed in the request cache + this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe( + filter((exists: boolean) => !exists), + switchMap(() => this.itemService.findById(this.item.uuid)), + getSucceededRemoteData(), + ).subscribe((itemRD: RemoteData) => { + this.item = itemRD.payload; + this.cdRef.detectChanges(); + }); } /** @@ -104,13 +122,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { switchMap((removedIds: string[]) => observableZip(...removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))), map((responses: RestResponse[]) => responses.filter((response: RestResponse) => response.isSuccessful)) ).subscribe((responses: RestResponse[]) => { - // Make sure the lists are up-to-date and send a notification that the removal was successful - // TODO: Fix lists refreshing correctly + // Remove the item's cache to make sure the lists are reloaded with the newest values this.objectCache.remove(this.item.self); this.requestService.removeByHrefSubstring(this.item.self); - // this.itemService.findById(this.item.id).pipe(getSucceededRemoteData(), take(1)).subscribe((itemRD: RemoteData) => this.item = itemRD.payload); this.initializeOriginalFields(); this.initializeUpdates(); + // Send a notification that the removal was successful this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); }); } @@ -125,33 +142,10 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { } /** - * Transform the item's relationships of a specific type into related items - * @param label The relationship type's label + * Unsubscribe from the item update when the component is destroyed */ - public getRelatedItemsByLabel(label: string): Observable { - return this.relationshipService.getRelatedItemsByLabel(this.item, label); - } - - /** - * Get FieldUpdates for the relationships of a specific type - * @param label The relationship type's label - */ - public getUpdatesByLabel(label: string): Observable { - return this.getRelatedItemsByLabel(label).pipe( - switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items)) - ) - } - - /** - * Get the i18n message key for a relationship - * @param label The relationship type's label - */ - public getRelationshipMessageKey(label: string): string { - if (label.indexOf('Of') > -1) { - return `relationships.${label.substring(0, label.indexOf('Of') + 2)}` - } else { - return label; - } + ngOnDestroy(): void { + this.itemUpdateSubscription.unsubscribe(); } } diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index d415da50b3..c1a78225b8 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -3,7 +3,7 @@ import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { applyPatch, Operation } from 'fast-json-patch'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, filter, map, mergeMap, take, } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, mergeMap, take, tap, } from 'rxjs/operators'; import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util'; import { CoreState } from '../core.reducers'; import { coreSelector } from '../core.selectors'; @@ -224,6 +224,18 @@ export class ObjectCacheService { return result; } + /** + * Create an observable that emits a new value whenever the availability of the cached object changes. + * The value it emits is a boolean stating if the object exists in cache or not. + * @param selfLink The self link of the object to observe + */ + hasBySelfLinkObservable(selfLink: string): Observable { + return this.store.pipe( + select(entryFromSelfLinkSelector(selfLink)), + map((entry: ObjectCacheEntry) => this.isValid(entry)) + ); + } + /** * Check whether an ObjectCacheEntry should still be cached * diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 82c2a47491..f15a60d5ec 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -265,4 +265,15 @@ export class RequestService { return result; } + /** + * Create an observable that emits a new value whenever the availability of the cached request changes. + * The value it emits is a boolean stating if the request exists in cache or not. + * @param href The href of the request to observe + */ + hasByHrefObservable(href: string): Observable { + return this.getByHref(href).pipe( + map((requestEntry: RequestEntry) => this.isValid(requestEntry)) + ); + } + } From eeb2c790c186da27d8254264b6658b97c8fad30a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Apr 2019 16:16:55 +0200 Subject: [PATCH 13/60] 61142: Fixed AoT build errors --- .../abstract-item-update.component.ts | 17 +++++++---------- .../item-metadata/item-metadata.component.ts | 14 +++++++++----- src/app/core/data/relationship.service.spec.ts | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts index 76e6eb9446..c49def3dd2 100644 --- a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts +++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injectable, OnInit } from '@angular/core'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Observable } from 'rxjs/internal/Observable'; import { Item } from '../../../core/shared/item.model'; @@ -11,10 +11,7 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { first, map } from 'rxjs/operators'; import { RemoteData } from '../../../core/data/remote-data'; -@Component({ - selector: 'ds-abstract-item-update', - template: ``, -}) +@Injectable() /** * Abstract component for managing object updates of an item */ @@ -22,25 +19,25 @@ export abstract class AbstractItemUpdateComponent implements OnInit { /** * The item to display the edit page for */ - protected item: Item; + item: Item; /** * The current values and updates for all this item's fields * Should be initialized in the initializeUpdates method of the child component */ - protected updates$: Observable; + updates$: Observable; /** * The current url of this page */ - protected url: string; + url: string; /** * Prefix for this component's notification translate keys * Should be initialized in the initializeNotificationsPrefix method of the child component */ - protected notificationsPrefix; + notificationsPrefix; /** * The time span for being able to undo discarding changes */ - protected discardTimeOut: number; + discardTimeOut: number; constructor( protected itemService: ItemDataService, diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index 6e8be0efb6..dbbcebfd00 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { ItemDataService } from '../../../core/data/item-data.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; @@ -60,7 +60,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { * Initialize the values and updates of the current item's metadata fields */ public initializeUpdates(): void { - this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); + this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.getMetadataAsListExcludingRelationships()); } /** @@ -82,7 +82,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { * Sends all initial values of this item to the object updates service */ public initializeOriginalFields() { - this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified); + this.objectUpdatesService.initialize(this.url, this.getMetadataAsListExcludingRelationships(), this.item.lastModified); } /** @@ -92,7 +92,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { public submit() { this.isValid().pipe(first()).subscribe((isValid) => { if (isValid) { - const metadata$: Observable = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable; + const metadata$: Observable = this.objectUpdatesService.getUpdatedFields(this.url, this.getMetadataAsListExcludingRelationships()) as Observable; metadata$.pipe( first(), switchMap((metadata: MetadatumViewModel[]) => { @@ -105,7 +105,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { (rd: RemoteData) => { this.item = rd.payload; this.initializeOriginalFields(); - this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); + this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.getMetadataAsListExcludingRelationships()); this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); } ) @@ -124,4 +124,8 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent { take(1), map((remoteData$) => remoteData$.payload.page.map((field: MetadataField) => field.toString()))); } + + getMetadataAsListExcludingRelationships(): MetadatumViewModel[] { + return this.item.metadataAsList.filter((metadata: MetadatumViewModel) => !metadata.key.startsWith('relation.') && !metadata.key.startsWith('relationship.')); + } } diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts index 0e417b7ffe..ce2b169eef 100644 --- a/src/app/core/data/relationship.service.spec.ts +++ b/src/app/core/data/relationship.service.spec.ts @@ -68,7 +68,7 @@ describe('RelationshipService', () => { const relatedItems = [relatedItem1, relatedItem2]; const itemService = jasmine.createSpyObj('itemService', { - findById: (uuid) => new RemoteData(false, false, true, undefined, relatedItems.filter((item) => item.id === uuid)[0]) + findById: (uuid) => new RemoteData(false, false, true, undefined, relatedItems.filter((relatedItem) => relatedItem.id === uuid)[0]) }); function initTestService() { From dacf8136764c5070ee59bc8bd6c15c2dce9200f6 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 18 Apr 2019 17:24:24 +0200 Subject: [PATCH 14/60] 61142: Notifications on failed relationship delete requests --- resources/i18n/en.json | 3 ++ .../item-relationships.component.ts | 29 ++++++++++++------- src/app/core/data/relationship.service.ts | 4 +-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 284fab6c82..eb6f600e21 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -296,6 +296,9 @@ "saved": { "title": "Relationships saved", "content": "Your changes to this item's relationships were saved." + }, + "failed": { + "title": "Error deleting relationship" } } } diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 7db8756c78..3e74794866 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { Observable } from 'rxjs/internal/Observable'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { ItemDataService } from '../../../core/data/item-data.service'; @@ -14,7 +14,7 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { RelationshipService } from '../../../core/data/relationship.service'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; -import { RestResponse } from '../../../core/cache/response.models'; +import { ErrorResponse, RestResponse } from '../../../core/cache/response.models'; import { isNotEmptyOperator } from '../../../shared/empty.util'; import { RemoteData } from '../../../core/data/remote-data'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; @@ -95,7 +95,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl /** * Resolve the currently selected related items back to relationships and send a delete request - * Make sure the lists are refreshed afterwards + * Make sure the lists are refreshed afterwards and notifications are sent for success and errors */ public submit(): void { // Get all IDs of related items of which their relationship with the current item is about to be removed @@ -119,16 +119,25 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl // Request a delete for every relationship found in the observable created above removedRelationshipIds$.pipe( take(1), - switchMap((removedIds: string[]) => observableZip(...removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))), - map((responses: RestResponse[]) => responses.filter((response: RestResponse) => response.isSuccessful)) + switchMap((removedIds: string[]) => observableZip(...removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))) ).subscribe((responses: RestResponse[]) => { - // Remove the item's cache to make sure the lists are reloaded with the newest values - this.objectCache.remove(this.item.self); - this.requestService.removeByHrefSubstring(this.item.self); + const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); + const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful); + + // Display an error notification for each failed request + failedResponses.forEach((response: ErrorResponse) => { + this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); + }); + if (successfulResponses.length > 0) { + // Remove the item's cache to make sure the lists are reloaded with the newest values + this.objectCache.remove(this.item.self); + this.requestService.removeByHrefSubstring(this.item.self); + // Send a notification that the removal was successful + this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); + } + // Reset the state of editing relationships this.initializeOriginalFields(); this.initializeUpdates(); - // Send a notification that the removal was successful - this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); }); } diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index d0308dead7..ba77fb4f5a 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -7,7 +7,7 @@ import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/o import { configureRequest, filterSuccessfulResponses, - getRemoteDataPayload, + getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators'; import { DeleteRequest, RestRequest } from './request.models'; @@ -60,7 +60,7 @@ export class RelationshipService { map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)), - filterSuccessfulResponses() + getResponseFromEntry() ); } From 5a06c9195e34b62ff734fefdf3bae89f44cd8da3 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 18 Apr 2019 17:42:38 +0200 Subject: [PATCH 15/60] 61142: Author seperator in publication list elements --- .../publication/publication-list-element.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/object-list/item-list-element/item-types/publication/publication-list-element.component.html b/src/app/shared/object-list/item-list-element/item-types/publication/publication-list-element.component.html index aff19aec1d..d467edfb21 100644 --- a/src/app/shared/object-list/item-list-element/item-types/publication/publication-list-element.component.html +++ b/src/app/shared/object-list/item-list-element/item-types/publication/publication-list-element.component.html @@ -12,6 +12,7 @@ class="item-list-authors"> + ; From 15ed0cc8fa5ce4ff87403e5d2eee3cb8902fd4ee Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 23 Apr 2019 14:32:11 +0200 Subject: [PATCH 16/60] 61947: Thumbnail display fix --- src/app/core/shared/item.model.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts index 645b50d5db..7bd69131c6 100644 --- a/src/app/core/shared/item.model.ts +++ b/src/app/core/shared/item.model.ts @@ -8,6 +8,7 @@ import { Bitstream } from './bitstream.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { PaginatedList } from '../data/paginated-list'; import { Relationship } from './item-relationships/relationship.model'; +import { getSucceededRemoteData } from './operators'; export class Item extends DSpaceObject { @@ -95,7 +96,7 @@ export class Item extends DSpaceObject { */ getBitstreamsByBundleName(bundleName: string): Observable { return this.bitstreams.pipe( - filter((rd: RemoteData>) => !rd.isResponsePending), + getSucceededRemoteData(), map((rd: RemoteData>) => rd.payload.page), filter((bitstreams: Bitstream[]) => hasValue(bitstreams)), take(1), From 16bad8256475dd351bd8ee9146a2a3ef2d487813 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 6 Jun 2019 18:00:54 +0200 Subject: [PATCH 17/60] 62741: Entities grid templates - publication, orgunit, person, project --- .../orgunit-grid-element.component.html | 33 +++++++++++++++++ .../orgunit-grid-element.component.scss | 0 .../orgunit-grid-element.component.spec.ts | 0 .../orgunit/orgunit-grid-element.component.ts | 14 +++++++ .../person/person-grid-element.component.html | 30 +++++++++++++++ .../person/person-grid-element.component.scss | 0 .../person-grid-element.component.spec.ts | 0 .../person/person-grid-element.component.ts | 14 +++++++ .../project-grid-element.component.html | 25 +++++++++++++ .../project-grid-element.component.scss | 0 .../project-grid-element.component.spec.ts | 0 .../project/project-grid-element.component.ts | 14 +++++++ .../research-entities.module.ts | 8 +++- src/app/shared/items/item-type-decorator.ts | 1 + .../grid-thumbnail.component.html | 3 +- .../grid-thumbnail.component.ts | 18 +++++++-- .../publication-grid-element.component.html | 34 +++++++++++++++++ .../publication-grid-element.component.scss | 0 ...publication-grid-element.component.spec.ts | 0 .../publication-grid-element.component.ts | 15 ++++++++ ...arch-result-grid-element.component.spec.ts | 0 ...em-search-result-grid-element.component.ts | 37 +++++++++++++++++++ ...-search-result-grid-element.component.html | 34 +---------------- ...em-search-result-grid-element.component.ts | 5 ++- .../search-result-grid-element.component.ts | 7 +++- ...em-search-result-list-element.component.ts | 2 +- .../item-type-badge.component.html | 3 ++ .../item-type-badge.component.ts | 10 +++++ ...-search-result-list-element.component.html | 4 +- src/app/shared/shared.module.ts | 8 +++- 30 files changed, 271 insertions(+), 48 deletions(-) create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts create mode 100644 src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts create mode 100644 src/app/shared/object-list/item-type-badge/item-type-badge.component.html create mode 100644 src/app/shared/object-list/item-type-badge/item-type-badge.component.ts diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html new file mode 100644 index 0000000000..afb7f8da09 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -0,0 +1,33 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + + +

+

+ + {{dso.firstMetadataValue('orgunit.identifier.country')}} + , + {{dso.firstMetadataValue('orgunit.identifier.city')}} + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts new file mode 100644 index 0000000000..f0c87eb975 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../shared/animations/focus'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; + +@rendersItemType('OrgUnit', ItemViewMode.Card) +@Component({ + selector: 'ds-orgunit-grid-element', + styleUrls: ['./orgunit-grid-element.component.scss'], + templateUrl: './orgunit-grid-element.component.html', + animations: [focusShadow] +}) +export class OrgunitGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html new file mode 100644 index 0000000000..05f1eefda1 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -0,0 +1,30 @@ + +
+ +
+ + +
+
+
+ + +

+
+ +

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts new file mode 100644 index 0000000000..3ec17c9ce5 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; +import { focusShadow } from '../../../../shared/animations/focus'; + +@rendersItemType('Person', ItemViewMode.Card) +@Component({ + selector: 'ds-person-grid-element', + styleUrls: ['./person-grid-element.component.scss'], + templateUrl: './person-grid-element.component.html', + animations: [focusShadow] +}) +export class PersonGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html new file mode 100644 index 0000000000..f01f0334d3 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -0,0 +1,25 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts new file mode 100644 index 0000000000..be246f0fbe --- /dev/null +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../shared/animations/focus'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; + +@rendersItemType('Project', ItemViewMode.Card) +@Component({ + selector: 'ds-project-grid-element', + styleUrls: ['./project-grid-element.component.scss'], + templateUrl: './project-grid-element.component.html', + animations: [focusShadow] +}) +export class ProjectGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts index ba28f174df..099fa2a6a3 100644 --- a/src/app/entity-groups/research-entities/research-entities.module.ts +++ b/src/app/entity-groups/research-entities/research-entities.module.ts @@ -11,6 +11,9 @@ import { PersonMetadataListElementComponent } from './item-list-elements/person/ import { PersonListElementComponent } from './item-list-elements/person/person-list-element.component'; import { ProjectListElementComponent } from './item-list-elements/project/project-list-element.component'; import { TooltipModule } from 'ngx-bootstrap'; +import { PersonGridElementComponent } from './item-grid-elements/person/person-grid-element.component'; +import { OrgunitGridElementComponent } from './item-grid-elements/orgunit/orgunit-grid-element.component'; +import { ProjectGridElementComponent } from './item-grid-elements/project/project-grid-element.component'; const ENTRY_COMPONENTS = [ OrgunitComponent, @@ -20,7 +23,10 @@ const ENTRY_COMPONENTS = [ OrgUnitMetadataListElementComponent, PersonListElementComponent, PersonMetadataListElementComponent, - ProjectListElementComponent + ProjectListElementComponent, + PersonGridElementComponent, + OrgunitGridElementComponent, + ProjectGridElementComponent ]; @NgModule({ diff --git a/src/app/shared/items/item-type-decorator.ts b/src/app/shared/items/item-type-decorator.ts index 2420e71908..3a040ae5bf 100644 --- a/src/app/shared/items/item-type-decorator.ts +++ b/src/app/shared/items/item-type-decorator.ts @@ -3,6 +3,7 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-represent export enum ItemViewMode { Element = 'element', + Card = 'card', Full = 'full', Metadata = 'metadata' } diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html index c0c3c1f65f..5b09d09a55 100644 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html +++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html @@ -1,4 +1,3 @@
- - +
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts index 8ca93470da..6ae0c2d37e 100644 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts +++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Bitstream } from '../../../core/shared/bitstream.model'; +import { hasValue } from '../../empty.util'; /** * This component renders a given Bitstream as a thumbnail. @@ -12,7 +13,7 @@ import { Bitstream } from '../../../core/shared/bitstream.model'; styleUrls: ['./grid-thumbnail.component.scss'], templateUrl: './grid-thumbnail.component.html' }) -export class GridThumbnailComponent { +export class GridThumbnailComponent implements OnInit { @Input() thumbnail: Bitstream; @@ -21,10 +22,19 @@ export class GridThumbnailComponent { /** * The default 'holder.js' image */ - holderSource = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+'; + @Input() defaultImage? = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+'; + src: string; errorHandler(event) { - event.currentTarget.src = this.holderSource; + event.currentTarget.src = this.defaultImage; + } + + ngOnInit(): void { + if (hasValue(this.thumbnail) && this.thumbnail.content) { + this.src = this.thumbnail.content; + } else { + this.src = this.defaultImage + } } } diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html new file mode 100644 index 0000000000..b0509eb5df --- /dev/null +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -0,0 +1,34 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + {{dso.firstMetadataValue('dc.date.issued')}} + , + + + +

+

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts new file mode 100644 index 0000000000..18dcccd1d2 --- /dev/null +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts @@ -0,0 +1,15 @@ +import { TypedItemSearchResultGridElementComponent } from '../typed-item-search-result-grid-element.component'; +import { DEFAULT_ITEM_TYPE, ItemViewMode, rendersItemType } from '../../../../items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../animations/focus'; + +@rendersItemType('Publication', ItemViewMode.Card) +@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Card) +@Component({ + selector: 'ds-publication-grid-element', + styleUrls: ['./publication-grid-element.component.scss'], + templateUrl: './publication-grid-element.component.html', + animations: [focusShadow] +}) +export class PublicationGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts new file mode 100644 index 0000000000..f4f470c052 --- /dev/null +++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts @@ -0,0 +1,37 @@ +import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { SearchResultGridElementComponent } from '../../search-result-grid-element/search-result-grid-element.component'; +import { TruncatableService } from '../../../truncatable/truncatable.service'; +import { Component, Inject } from '@angular/core'; +import { ITEM } from '../../../items/switcher/item-type-switcher.component'; +import { hasValue } from '../../../empty.util'; +import { MetadataMap } from '../../../../core/shared/metadata.models'; + +/** + * A generic component for displaying item grid elements + */ +@Component({ + selector: 'ds-item-search-result-grid-element', + template: '' +}) +export class TypedItemSearchResultGridElementComponent extends SearchResultGridElementComponent { + item: Item; + + constructor( + protected truncatableService: TruncatableService, + @Inject(ITEM) public obj: Item | ItemSearchResult, + ) { + super(undefined, truncatableService); + if (hasValue((obj as any).indexableObject)) { + this.object = obj as ItemSearchResult; + this.dso = this.object.indexableObject; + } else { + this.object = { + indexableObject: obj as Item, + hitHighlights: new MetadataMap() + }; + this.dso = obj as Item; + } + this.item = this.dso; + } +} diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html index c7e2f524f3..d433c7acf2 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html @@ -1,33 +1 @@ - -
- -
- - -
-
-
- -

-
-

- - {{dso.firstMetadataValue('dc.date.issued')}} - , - - - -

-

- - - -

-
- View -
-
-
-
+ diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts index 30c36b3af9..7bbe41fe60 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts @@ -6,6 +6,7 @@ import { Item } from '../../../../core/shared/item.model'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; import { SetViewMode } from '../../../view-mode'; import { focusShadow } from '../../../../shared/animations/focus'; +import { ItemViewMode } from '../../../items/item-type-decorator'; @Component({ selector: 'ds-item-search-result-grid-element', @@ -15,4 +16,6 @@ import { focusShadow } from '../../../../shared/animations/focus'; }) @renderElementsFor(ItemSearchResult, SetViewMode.Grid) -export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent {} +export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent { + viewMode = ItemViewMode.Card; +} diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts index 0961dc96ee..5f31d52ae7 100644 --- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts @@ -7,6 +7,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m import { TruncatableService } from '../../truncatable/truncatable.service'; import { Observable } from 'rxjs'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { hasValue } from '../../empty.util'; @Component({ selector: 'ds-search-result-grid-element', @@ -16,9 +17,11 @@ import { Metadata } from '../../../core/shared/metadata.utils'; export class SearchResultGridElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent { dso: K; - public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) { + public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, protected truncatableService: TruncatableService) { super(listableObject); - this.dso = this.object.indexableObject; + if (hasValue(this.object)) { + this.dso = this.object.indexableObject; + } } /** diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts index 7df3ab5681..dd1b5a7e5f 100644 --- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts @@ -11,7 +11,7 @@ import { MetadataMap } from '../../../../core/shared/metadata.models'; * A generic component for displaying item list elements */ @Component({ - selector: 'ds-item-search-result', + selector: 'ds-item-search-result-list-element', template: '' }) export class TypedItemSearchResultListElementComponent extends SearchResultListElementComponent { diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.html b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html new file mode 100644 index 0000000000..35d7663801 --- /dev/null +++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html @@ -0,0 +1,3 @@ +
+ {{ type.toLowerCase() + '.listelement.badge' | translate }} +
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts new file mode 100644 index 0000000000..53e36a535d --- /dev/null +++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; +import { ListableObject } from '../../object-collection/shared/listable-object.model'; + +@Component({ + selector: 'ds-item-type-badge', + templateUrl: './item-type-badge.component.html' +}) +export class ItemTypeBadgeComponent { + @Input() object: ListableObject; +} diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html index a2617a956f..051a27bde7 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html @@ -1,4 +1,2 @@ -
- {{ type.toLowerCase() + '.listelement.badge' | translate }} -
+ diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 816139c8b9..66afb1e41b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -138,6 +138,9 @@ import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; +import { TypedItemSearchResultGridElementComponent } from './object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.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'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -256,8 +259,10 @@ const COMPONENTS = [ CollectionSearchResultListElementComponent, ItemSearchResultListElementComponent, TypedItemSearchResultListElementComponent, + TypedItemSearchResultGridElementComponent, ItemTypeSwitcherComponent, - BrowseByComponent + BrowseByComponent, + ItemTypeBadgeComponent ]; const ENTRY_COMPONENTS = [ @@ -275,6 +280,7 @@ const ENTRY_COMPONENTS = [ CommunityGridElementComponent, SearchResultGridElementComponent, PublicationListElementComponent, + PublicationGridElementComponent, BrowseEntryListElementComponent, MyDSpaceResultDetailElementComponent, SearchResultGridElementComponent, From 22ae5a04e7959be8bd32f4aada2ba6a5a3bff23a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Jun 2019 12:51:27 +0200 Subject: [PATCH 18/60] 62741: Journal-types grid elements & grid thumbnail fix/improvement --- .../journal-issue-grid-element.component.html | 30 +++++++++++++++++ .../journal-issue-grid-element.component.scss | 0 ...urnal-issue-grid-element.component.spec.ts | 0 .../journal-issue-grid-element.component.ts | 14 ++++++++ ...journal-volume-grid-element.component.html | 30 +++++++++++++++++ ...journal-volume-grid-element.component.scss | 0 ...rnal-volume-grid-element.component.spec.ts | 0 .../journal-volume-grid-element.component.ts | 14 ++++++++ .../journal-grid-element.component.html | 33 +++++++++++++++++++ .../journal-grid-element.component.scss | 0 .../journal-grid-element.component.spec.ts | 0 .../journal/journal-grid-element.component.ts | 14 ++++++++ .../journal-entities.module.ts | 8 ++++- .../orgunit-grid-element.component.html | 2 +- .../person/person-grid-element.component.html | 2 +- .../project-grid-element.component.html | 2 +- .../publication-grid-element.component.html | 2 +- .../object-grid/object-grid.component.scss | 5 +++ 18 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts create mode 100644 src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html new file mode 100644 index 0000000000..6bd296db48 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -0,0 +1,30 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + + +

+

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts new file mode 100644 index 0000000000..538971bf84 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../shared/animations/focus'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; + +@rendersItemType('JournalIssue', ItemViewMode.Card) +@Component({ + selector: 'ds-journal-issue-grid-element', + styleUrls: ['./journal-issue-grid-element.component.scss'], + templateUrl: './journal-issue-grid-element.component.html', + animations: [focusShadow] +}) +export class JournalIssueGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html new file mode 100644 index 0000000000..e1d3cb5e0a --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -0,0 +1,30 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + + +

+

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts new file mode 100644 index 0000000000..3ef4e9948e --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../shared/animations/focus'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; + +@rendersItemType('JournalVolume', ItemViewMode.Card) +@Component({ + selector: 'ds-journal-volume-grid-element', + styleUrls: ['./journal-volume-grid-element.component.scss'], + templateUrl: './journal-volume-grid-element.component.html', + animations: [focusShadow] +}) +export class JournalVolumeGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html new file mode 100644 index 0000000000..54fc0e4215 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -0,0 +1,33 @@ + +
+ +
+ + +
+
+
+ + +

+
+

+ + {{dso.firstMetadataValue('journal.contributor.editor')}} + , + {{dso.firstMetadataValue('journal.publisher')}} + +

+

+ + + +

+
+ View +
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts new file mode 100644 index 0000000000..37c0b0aad1 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts @@ -0,0 +1,14 @@ +import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator'; +import { Component } from '@angular/core'; +import { focusShadow } from '../../../../shared/animations/focus'; +import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component'; + +@rendersItemType('Journal', ItemViewMode.Card) +@Component({ + selector: 'ds-journal-grid-element', + styleUrls: ['./journal-grid-element.component.scss'], + templateUrl: './journal-grid-element.component.html', + animations: [focusShadow] +}) +export class JournalGridElementComponent extends TypedItemSearchResultGridElementComponent { +} diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts index 50ec160650..4033645e1b 100644 --- a/src/app/entity-groups/journal-entities/journal-entities.module.ts +++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts @@ -9,6 +9,9 @@ import { JournalListElementComponent } from './item-list-elements/journal/journa import { JournalIssueListElementComponent } from './item-list-elements/journal-issue/journal-issue-list-element.component'; import { JournalVolumeListElementComponent } from './item-list-elements/journal-volume/journal-volume-list-element.component'; import { TooltipModule } from 'ngx-bootstrap'; +import { JournalIssueGridElementComponent } from './item-grid-elements/journal-issue/journal-issue-grid-element.component'; +import { JournalVolumeGridElementComponent } from './item-grid-elements/journal-volume/journal-volume-grid-element.component'; +import { JournalGridElementComponent } from './item-grid-elements/journal/journal-grid-element.component'; const ENTRY_COMPONENTS = [ JournalComponent, @@ -16,7 +19,10 @@ const ENTRY_COMPONENTS = [ JournalVolumeComponent, JournalListElementComponent, JournalIssueListElementComponent, - JournalVolumeListElementComponent + JournalVolumeListElementComponent, + JournalIssueGridElementComponent, + JournalVolumeGridElementComponent, + JournalGridElementComponent ]; @NgModule({ diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index afb7f8da09..cb5d9b59af 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index 05f1eefda1..463007d099 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index f01f0334d3..1f122895ee 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html index b0509eb5df..06cf8496c4 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss index ff78634863..fdd6c6e7ac 100644 --- a/src/app/shared/object-grid/object-grid.component.scss +++ b/src/app/shared/object-grid/object-grid.component.scss @@ -7,6 +7,11 @@ ds-wrapper-grid-element ::ng-deep { div.thumbnail > img { height: $card-thumbnail-height; width: 100%; + display: block; + min-width: 100%; + min-height: 100%; + object-fit: cover; + object-position: 50% 15%; } div.card { margin-top: $ds-wrapper-grid-spacing; From 0a32d3f91579daa2eaeb445a16efd4dd79cb3809 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Jun 2019 13:23:03 +0200 Subject: [PATCH 19/60] 62741: Remove whitespace and entity thumbnails from grid templates --- .../journal-issue-grid-element.component.html | 6 +++--- .../journal-volume-grid-element.component.html | 6 +++--- .../journal/journal-grid-element.component.html | 6 +++--- .../orgunit/orgunit-grid-element.component.html | 8 ++++---- .../person/person-grid-element.component.html | 8 ++++---- .../project/project-grid-element.component.html | 6 +++--- .../publication/publication-grid-element.component.html | 6 +++--- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html index 6bd296db48..11c1911eda 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -8,16 +8,16 @@
- +

- +

- +

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html index e1d3cb5e0a..aaa22d48f6 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -8,16 +8,16 @@
- +

- +

- +

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html index 54fc0e4215..d532fb60d4 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -8,19 +8,19 @@
- +

- + {{dso.firstMetadataValue('journal.contributor.editor')}} , {{dso.firstMetadataValue('journal.publisher')}}

- +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index cb5d9b59af..d4150bdc9a 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -2,23 +2,23 @@
- +
- +

- +

- + {{dso.firstMetadataValue('orgunit.identifier.country')}} , {{dso.firstMetadataValue('orgunit.identifier.city')}} diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index 463007d099..c47e96bcc1 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -2,22 +2,22 @@

- +
- +

- +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index 1f122895ee..16083c5787 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -2,17 +2,17 @@
- +
- +

- +

diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html index 06cf8496c4..3bb21b1f1c 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -8,12 +8,12 @@
- +

- + {{dso.firstMetadataValue('dc.date.issued')}} , @@ -21,7 +21,7 @@

- +

From 47994f955764beb08db5d0957eb52016c78c6afb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Jun 2019 15:18:38 +0200 Subject: [PATCH 20/60] 62741: Entities grid templates tests --- .../journal-issue-grid-element.component.html | 4 +- ...urnal-issue-grid-element.component.spec.ts | 47 +++++++ ...journal-volume-grid-element.component.html | 4 +- ...rnal-volume-grid-element.component.spec.ts | 47 +++++++ .../journal-grid-element.component.html | 6 +- .../journal-grid-element.component.spec.ts | 53 ++++++++ .../orgunit-grid-element.component.html | 6 +- .../orgunit-grid-element.component.spec.ts | 53 ++++++++ .../person/person-grid-element.component.html | 6 +- .../person-grid-element.component.spec.ts | 47 +++++++ .../project-grid-element.component.html | 2 +- .../project-grid-element.component.spec.ts | 41 ++++++ .../grid-thumbnail.component.spec.ts | 4 +- .../publication-grid-element.component.html | 2 +- ...publication-grid-element.component.spec.ts | 124 ++++++++++++++++++ ...arch-result-grid-element.component.spec.ts | 83 ++++++++++++ ...arch-result-grid-element.component.spec.ts | 89 ++----------- ...arch-result-list-element.component.spec.ts | 2 +- .../item-type-badge.component.spec.ts | 83 ++++++++++++ ...arch-result-list-element.component.spec.ts | 41 +----- 20 files changed, 612 insertions(+), 132 deletions(-) create mode 100644 src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html index 11c1911eda..93d3954f2f 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -11,12 +11,12 @@

-

+

-

+

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts index e69de29bb2..3af407d0f8 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts @@ -0,0 +1,47 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'journalissue.issuedate': [ + { + language: null, + value: '2015-06-26' + } + ], + 'journal.title': [ + { + language: 'en_US', + value: 'The journal title' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('JournalIssueGridElementComponent', getEntityGridElementTestComponent(JournalIssueGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'journal-title'])); diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html index aaa22d48f6..f5487a34cf 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -11,12 +11,12 @@

-

+

-

+

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts index e69de29bb2..4751ed4cd8 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts @@ -0,0 +1,47 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'journalvolume.issuedate': [ + { + language: null, + value: '2015-06-26' + } + ], + 'journalvolume.identifier.description': [ + { + language: 'en_US', + value: 'A description for the journal volume' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('JournalVolumeGridElementComponent', getEntityGridElementTestComponent(JournalVolumeGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'description'])); diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html index d532fb60d4..d4354efcf4 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -14,12 +14,12 @@

- {{dso.firstMetadataValue('journal.contributor.editor')}} + {{dso.firstMetadataValue('journal.contributor.editor')}} , - {{dso.firstMetadataValue('journal.publisher')}} + {{dso.firstMetadataValue('journal.publisher')}}

-

+

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts index e69de29bb2..d9934e2d2f 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts @@ -0,0 +1,53 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { JournalGridElementComponent } from './journal-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'journal.contributor.editor': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'journal.publisher': [ + { + language: 'en_US', + value: 'A company' + } + ], + 'journal.identifier.description': [ + { + language: 'en_US', + value: 'This is the description' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('JournalGridElementComponent', getEntityGridElementTestComponent(JournalGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['editor', 'publisher', 'description'])); diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index d4150bdc9a..92fffd0166 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -11,7 +11,7 @@

-

+

@@ -19,9 +19,9 @@

- {{dso.firstMetadataValue('orgunit.identifier.country')}} + {{dso.firstMetadataValue('orgunit.identifier.country')}} , - {{dso.firstMetadataValue('orgunit.identifier.city')}} + {{dso.firstMetadataValue('orgunit.identifier.city')}}

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts index e69de29bb2..25249fd2b0 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts @@ -0,0 +1,53 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { OrgunitGridElementComponent } from './orgunit-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'orgunit.identifier.dateestablished': [ + { + language: null, + value: '2015-06-26' + } + ], + 'orgunit.identifier.country': [ + { + language: 'en_US', + value: 'Belgium' + } + ], + 'orgunit.identifier.city': [ + { + language: 'en_US', + value: 'Brussels' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('OrgunitGridElementComponent', getEntityGridElementTestComponent(OrgunitGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'country', 'city'])); diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index c47e96bcc1..badc241b65 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -11,12 +11,12 @@

- -

+

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts index e69de29bb2..b3343a0605 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts @@ -0,0 +1,47 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { PersonGridElementComponent } from './person-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'person.identifier.email': [ + { + language: 'en_US', + value: 'Smith-Donald@gmail.com' + } + ], + 'person.identifier.jobtitle': [ + { + language: 'en_US', + value: 'Web Developer' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('PersonGridElementComponent', getEntityGridElementTestComponent(PersonGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['email', 'jobtitle'])); diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index 16083c5787..1fe4d18dae 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -11,7 +11,7 @@

-

+

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts index e69de29bb2..bcf19ed96a 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts @@ -0,0 +1,41 @@ +import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model'; +import { Item } from '../../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; +import { ProjectGridElementComponent } from './project-grid-element.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'project.identifier.funder': [ + { + language: 'en_US', + value: 'The project funder' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('ProjectGridElementComponent', getEntityGridElementTestComponent(ProjectGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['funder'])); diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts index 2d2bd6305a..170ca34b42 100644 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts +++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts @@ -6,7 +6,7 @@ import { GridThumbnailComponent } from './grid-thumbnail.component'; import { Bitstream } from '../../../core/shared/bitstream.model'; import { SafeUrlPipe } from '../../utils/safe-url-pipe'; -describe('ThumbnailComponent', () => { +describe('GridThumbnailComponent', () => { let comp: GridThumbnailComponent; let fixture: ComponentFixture; let de: DebugElement; @@ -36,7 +36,7 @@ describe('ThumbnailComponent', () => { it('should display placeholder', () => { fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(comp.holderSource); + expect(image.getAttribute('src')).toBe(comp.defaultImage); }); }); diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html index 3bb21b1f1c..e2477524ca 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -20,7 +20,7 @@

-

+

diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts index e69de29bb2..f067a21ae0 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts @@ -0,0 +1,124 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +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 { of as observableOf } from 'rxjs/internal/observable/of'; +import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { ITEM } from '../../../../items/switcher/item-type-switcher.component'; + +const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithMetadata.hitHighlights = {}; +mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '2015-06-26' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is an abstract' + } + ] + } +}); + +const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutMetadata.hitHighlights = {}; +mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('PublicationGridElementComponent', getEntityGridElementTestComponent(PublicationGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['authors', 'date', 'abstract'])); + +/** + * Create test cases for a grid component of an entity. + * @param component The component's class + * @param searchResultWithMetadata An ItemSearchResult containing an item with metadata that should be displayed in the grid element + * @param searchResultWithoutMetadata An ItemSearchResult containing an item that's missing the metadata that should be displayed in the grid element + * @param fieldsToCheck A list of fields to check. The tests expect to find html elements with class ".item-${field}", so make sure they exist in the html template of the grid element. + * For example: If one of the fields to check is labeled "authors", the html template should contain at least one element with class ".item-authors" that's + * present when the author metadata is available. + */ +export function getEntityGridElementTestComponent(component, searchResultWithMetadata: ItemSearchResult, searchResultWithoutMetadata: ItemSearchResult, fieldsToCheck: string[]) { + return () => { + let comp; + let fixture; + + const truncatableServiceStub: any = { + isCollapsed: (id: number) => observableOf(true), + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule], + declarations: [component, TruncatePipe], + providers: [ + { provide: TruncatableService, useValue: truncatableServiceStub }, + {provide: ITEM, useValue: searchResultWithoutMetadata} + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(component, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(component); + comp = fixture.componentInstance; + })); + + fieldsToCheck.forEach((field) => { + describe(`when the item has "${field}" metadata`, () => { + beforeEach(() => { + comp.dso = searchResultWithMetadata.indexableObject; + fixture.detectChanges(); + }); + + it(`should show the "${field}" field`, () => { + const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`)); + expect(itemAuthorField).not.toBeNull(); + }); + }); + + describe(`when the item has no "${field}" metadata`, () => { + beforeEach(() => { + comp.dso = searchResultWithoutMetadata.indexableObject; + fixture.detectChanges(); + }); + + it(`should not show the "${field}" field`, () => { + const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`)); + expect(itemAuthorField).toBeNull(); + }); + }); + }); + } +} diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts index e69de29bb2..e4ace8d0b2 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts @@ -0,0 +1,83 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TruncatePipe } from '../../../utils/truncate.pipe'; +import { TruncatableService } from '../../../truncatable/truncatable.service'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { Item } from '../../../../core/shared/item.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; +import { ITEM } from '../../../items/switcher/item-type-switcher.component'; +import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; +import { createRelationshipsObservable } from '../../../../+item-page/simple/item-types/shared/item.component.spec'; +import { of as observableOf } from 'rxjs'; +import { MetadataMap } from '../../../../core/shared/metadata.models'; +import { TypedItemSearchResultGridElementComponent } from './typed-item-search-result-grid-element.component'; + +const mockItem: Item = Object.assign(new Item(), { + bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))), + metadata: [], + relationships: createRelationshipsObservable() +}); +const mockSearchResult = { + indexableObject: mockItem as Item, + hitHighlights: new MetadataMap() +} as ItemSearchResult; + +describe('TypedItemSearchResultGridElementComponent', () => { + let comp: TypedItemSearchResultGridElementComponent; + let fixture: ComponentFixture; + + describe('when injecting an Item', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe], + providers: [ + {provide: TruncatableService, useValue: {}}, + {provide: ITEM, useValue: mockItem} + ], + + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(TypedItemSearchResultGridElementComponent, { + set: {changeDetection: ChangeDetectionStrategy.Default} + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent); + comp = fixture.componentInstance; + })); + + it('should initiate item, object and dso correctly', () => { + expect(comp.item).toBe(mockItem); + expect(comp.dso).toBe(mockItem); + expect(comp.object.indexableObject).toBe(mockItem); + }) + }); + + describe('when injecting an ItemSearchResult', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe], + providers: [ + {provide: TruncatableService, useValue: {}}, + {provide: ITEM, useValue: mockSearchResult} + ], + + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(TypedItemSearchResultGridElementComponent, { + set: {changeDetection: ChangeDetectionStrategy.Default} + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent); + comp = fixture.componentInstance; + })); + + it('should initiate item, object and dso correctly', () => { + expect(comp.item).toBe(mockItem); + expect(comp.dso).toBe(mockItem); + expect(comp.object.indexableObject).toBe(mockItem); + }) + }); +}); diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts index 655fd268a7..282478ec33 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts @@ -8,6 +8,7 @@ import { Item } from '../../../../core/shared/item.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; +import { ItemViewMode } from '../../../items/item-type-decorator'; let itemSearchResultGridElementComponent: ItemSearchResultGridElementComponent; let fixture: ComponentFixture; @@ -16,41 +17,17 @@ const truncatableServiceStub: any = { isCollapsed: (id: number) => observableOf(true), }; -const mockItemWithAuthorAndDate: ItemSearchResult = new ItemSearchResult(); -mockItemWithAuthorAndDate.hitHighlights = {}; -mockItemWithAuthorAndDate.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), - metadata: { - 'dc.contributor.author': [ - { - language: 'en_US', - value: 'Smith, Donald' - } - ], - 'dc.date.issued': [ - { - language: null, - value: '2015-06-26' - } - ] - } -}); +const type = 'authorOfPublication'; -const mockItemWithoutAuthorAndDate: ItemSearchResult = new ItemSearchResult(); -mockItemWithoutAuthorAndDate.hitHighlights = {}; -mockItemWithoutAuthorAndDate.indexableObject = Object.assign(new Item(), { +const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult(); +mockItemWithRelationshipType.hitHighlights = {}; +mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), { bitstreams: observableOf({}), metadata: { - 'dc.title': [ + 'relationship.type': [ { language: 'en_US', - value: 'This is just another title' - } - ], - 'dc.type': [ - { - language: null, - value: 'Article' + value: type } ] } @@ -63,7 +40,7 @@ describe('ItemSearchResultGridElementComponent', () => { declarations: [ItemSearchResultGridElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, - { provide: 'objectElementProvider', useValue: (mockItemWithoutAuthorAndDate) } + { provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ItemSearchResultGridElementComponent, { @@ -76,51 +53,9 @@ describe('ItemSearchResultGridElementComponent', () => { itemSearchResultGridElementComponent = fixture.componentInstance; })); - describe('When the item has an author', () => { - beforeEach(() => { - itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject; - fixture.detectChanges(); - }); - - it('should show the author paragraph', () => { - const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors')); - expect(itemAuthorField).not.toBeNull(); - }); - }); - - describe('When the item has no author', () => { - beforeEach(() => { - itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject; - fixture.detectChanges(); - }); - - it('should not show the author paragraph', () => { - const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors')); - expect(itemAuthorField).toBeNull(); - }); - }); - - describe('When the item has an issuedate', () => { - beforeEach(() => { - itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject; - fixture.detectChanges(); - }); - - it('should show the issuedate span', () => { - const itemAuthorField = fixture.debugElement.query(By.css('span.item-date')); - expect(itemAuthorField).not.toBeNull(); - }); - }); - - describe('When the item has no issuedate', () => { - beforeEach(() => { - itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject; - fixture.detectChanges(); - }); - - it('should not show the issuedate span', () => { - const dateField = fixture.debugElement.query(By.css('span.item-date')); - expect(dateField).toBeNull(); - }); + it('should show send the object to item-type-switcher using viewMode "Card"', () => { + const itemTypeSwitcherComp = fixture.debugElement.query(By.css('ds-item-type-switcher')).componentInstance; + expect(itemTypeSwitcherComp.object).toBe(mockItemWithRelationshipType); + expect(itemTypeSwitcherComp.viewMode).toEqual(ItemViewMode.Card); }); }); diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts index f320ff2efc..b100f584e2 100644 --- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts @@ -23,7 +23,7 @@ const mockSearchResult = { hitHighlights: new MetadataMap() } as ItemSearchResult; -describe('ItemSearchResultComponent', () => { +describe('TypedItemSearchResultListElementComponent', () => { let comp: TypedItemSearchResultListElementComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts new file mode 100644 index 0000000000..04c40b73ff --- /dev/null +++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts @@ -0,0 +1,83 @@ +import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model'; +import { Item } from '../../../core/shared/item.model'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +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'; + +let comp: ItemTypeBadgeComponent; +let fixture: ComponentFixture; + +const type = 'authorOfPublication'; + +const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult(); +mockItemWithRelationshipType.hitHighlights = {}; +mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'relationship.type': [ + { + language: 'en_US', + value: type + } + ] + } +}); + +const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult(); +mockItemWithoutRelationshipType.hitHighlights = {}; +mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), { + bitstreams: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ] + } +}); + +describe('ItemTypeBadgeComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ItemTypeBadgeComponent, TruncatePipe], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(ItemTypeBadgeComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(ItemTypeBadgeComponent); + comp = fixture.componentInstance; + })); + + describe('When the item has a relationship type', () => { + beforeEach(() => { + comp.object = mockItemWithRelationshipType; + fixture.detectChanges(); + }); + + it('should show the relationship type badge', () => { + const badge = fixture.debugElement.query(By.css('span.badge')); + expect(badge.nativeElement.textContent).toContain(type.toLowerCase()); + }); + }); + + describe('When the item has no relationship type', () => { + beforeEach(() => { + comp.object = mockItemWithoutRelationshipType; + fixture.detectChanges(); + }); + + it('should not show a badge', () => { + const badge = fixture.debugElement.query(By.css('span.badge')); + expect(badge).toBeNull(); + }); + }); +}); diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts index a370d3a632..8f41018404 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts @@ -33,20 +33,6 @@ mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), { } }); -const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult(); -mockItemWithoutRelationshipType.hitHighlights = {}; -mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), - metadata: { - 'dc.title': [ - { - language: 'en_US', - value: 'This is just another title' - } - ] - } -}); - describe('ItemSearchResultListElementComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -54,7 +40,7 @@ describe('ItemSearchResultListElementComponent', () => { declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, - { provide: 'objectElementProvider', useValue: (mockItemWithoutRelationshipType) } + { provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ItemSearchResultListElementComponent, { @@ -67,27 +53,8 @@ describe('ItemSearchResultListElementComponent', () => { itemSearchResultListElementComponent = fixture.componentInstance; })); - describe('When the item has a relationship type', () => { - beforeEach(() => { - itemSearchResultListElementComponent.object = mockItemWithRelationshipType; - fixture.detectChanges(); - }); - - it('should show the relationship type badge', () => { - const badge = fixture.debugElement.query(By.css('span.badge')); - expect(badge.nativeElement.textContent).toContain(type.toLowerCase()); - }); - }); - - describe('When the item has no relationship type', () => { - beforeEach(() => { - itemSearchResultListElementComponent.object = mockItemWithoutRelationshipType; - fixture.detectChanges(); - }); - - it('should not show a badge', () => { - const badge = fixture.debugElement.query(By.css('span.badge')); - expect(badge).toBeNull(); - }); + it('should show a badge on top of the list element', () => { + const badge = fixture.debugElement.query(By.css('ds-item-type-badge')).componentInstance; + expect(badge.object).toBe(mockItemWithRelationshipType); }); }); From 37fd04593b861cf82e45f9e762ec9a568d3ee06a Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 14 Jun 2019 16:15:45 +0200 Subject: [PATCH 21/60] 62355: replaced all method calls in templates (except for metadata values...) --- .../search-authority-filter.component.html | 2 +- .../search-facet-option.component.html | 2 +- .../search-facet-option.component.ts | 4 +- .../search-facet-range-option.component.html | 2 +- .../search-facet-range-option.component.ts | 5 +- ...earch-facet-selected-option.component.html | 2 +- .../search-facet-selected-option.component.ts | 5 +- .../search-facet-filter.component.ts | 13 ++-- .../search-filter/search-filter.service.ts | 6 +- .../search-hierarchy-filter.component.html | 2 +- .../search-range-filter.component.html | 2 +- .../search-text-filter.component.html | 2 +- .../search-filters.component.html | 2 +- .../search-filters.component.ts | 5 +- .../search-label/search-label.component.html | 6 ++ .../search-label/search-label.component.ts | 75 +++++++++++++++++++ .../search-labels.component.html | 12 +-- .../search-labels/search-labels.component.ts | 45 +---------- .../+search-page/search-page.component.html | 6 +- src/app/+search-page/search-page.component.ts | 9 ++- src/app/+search-page/search-page.module.ts | 2 + .../item-type-switcher.component.html | 2 +- .../switcher/item-type-switcher.component.ts | 6 +- .../object-collection.component.html | 6 +- .../object-collection.component.spec.ts | 8 +- .../object-collection.component.ts | 38 +++------- .../wrapper-detail-element.component.html | 2 +- .../wrapper-detail-element.component.ts | 6 +- ...-search-result-grid-element.component.html | 2 +- .../search-result-grid-element.component.ts | 4 +- .../wrapper-grid-element.component.html | 2 +- .../wrapper-grid-element.component.ts | 3 +- .../search-result-list-element.component.ts | 2 + .../wrapper-list-element.component.html | 2 +- .../wrapper-list-element.component.ts | 4 +- 35 files changed, 166 insertions(+), 130 deletions(-) create mode 100644 src/app/+search-page/search-labels/search-label/search-label.component.html create mode 100644 src/app/+search-page/search-labels/search-label/search-label.component.ts diff --git a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index 76cdc6c8f5..63d034c6ea 100644 --- a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -17,7 +17,7 @@

{{filterValue.value}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts index 1fccee3736..1488f7a1e1 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts @@ -50,6 +50,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { */ addQueryParams; + searchLink: string; /** * Subscription to unsubscribe from on destroy */ @@ -66,6 +67,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.searchLink = this.getSearchLink(); this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked)); this.sub = observableCombineLatest(this.selectedValues$, this.searchConfigService.searchOptions) .subscribe(([selectedValues, searchOptions]) => { @@ -83,7 +85,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html index 8e8ad9b4e3..577e3e3c1c 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html @@ -1,5 +1,5 @@ {{filterValue.label}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts index 54d5d535df..1c243adfee 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts @@ -55,6 +55,8 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + searchLink: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, @@ -66,6 +68,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.searchLink = this.getSearchLink(); this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked)); this.sub = this.searchConfigService.searchOptions.subscribe(() => { this.updateChangeParams() @@ -82,7 +85,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html index 5657bd224e..5198433207 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html @@ -1,5 +1,5 @@ {{selectedValue.label}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts index 78dde92c2b..123a32dfb4 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts @@ -49,6 +49,8 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + searchLink: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, @@ -64,12 +66,13 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { .subscribe(([selectedValues, searchOptions]) => { this.updateRemoveParams(selectedValues) }); + this.searchLink = this.getSearchLink(); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index ee980a0599..ccbda54f89 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -80,6 +80,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { */ searchOptions$: Observable; + /** + * The current URL + */ + currentUrl: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected rdbs: RemoteDataBuildService, @@ -93,6 +98,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.currentUrl = this.router.url; this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined)); this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); @@ -215,13 +221,6 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return this.filterService.getPage(this.filterConfig.name); } - /** - * @returns {string} the current URL - */ - getCurrentUrl() { - return this.router.url; - } - /** * Submits a new active custom value to the filter from the input field * @param data The string from the input field diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 4b12417084..6024ad7249 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -1,5 +1,5 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { mergeMap, map, distinctUntilChanged } from 'rxjs/operators'; +import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators'; import { Injectable, InjectionToken } from '@angular/core'; import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; @@ -17,12 +17,8 @@ import { SearchFilterConfig } from '../../search-service/search-filter-config.mo import { RouteService } from '../../../shared/services/route.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { SearchOptions } from '../../search-options.model'; -import { PaginatedSearchOptions } from '../../paginated-search-options.model'; import { SearchFixedFilterService } from './search-fixed-filter.service'; import { Params } from '@angular/router'; -import * as postcss from 'postcss'; -import prefix = postcss.vendor.prefix; // const spy = create(); const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index ac2a72f4b6..996fd7f751 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -17,7 +17,7 @@
+ [action]="currentUrl">
-
{{"search.filters.reset" | translate}} +{{"search.filters.reset" | translate}} diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index e970647747..ba63d143c6 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -37,6 +37,8 @@ export class SearchFiltersComponent implements OnInit { */ @Input() inPlaceSearch; + searchLink: string; + /** * Initialize instance variables * @param {SearchService} searchService @@ -60,12 +62,13 @@ export class SearchFiltersComponent implements OnInit { Object.keys(filters).forEach((f) => filters[f] = null); return filters; })); + this.searchLink = this.getSearchLink(); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.html b/src/app/+search-page/search-labels/search-label/search-label.component.html new file mode 100644 index 0000000000..391efcb763 --- /dev/null +++ b/src/app/+search-page/search-labels/search-label/search-label.component.html @@ -0,0 +1,6 @@ + + {{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}} + × + \ No newline at end of file diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.ts b/src/app/+search-page/search-labels/search-label/search-label.component.ts new file mode 100644 index 0000000000..ab58e1bf4e --- /dev/null +++ b/src/app/+search-page/search-labels/search-label/search-label.component.ts @@ -0,0 +1,75 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Params } from '@angular/router'; +import { SearchService } from '../../search-service/search.service'; +import { map } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; + +@Component({ + selector: 'ds-search-label', + templateUrl: './search-label.component.html', +}) + +/** + * Component that represents the labels containing the currently active filters + */ +export class SearchLabelComponent implements OnInit { + @Input() key: string; + @Input() value: string; + @Input() inPlaceSearch: boolean; + @Input() appliedFilters: Observable; + searchLink: string; + removeParameters: Observable; + + /** + * Initialize the instance variable + */ + constructor( + private searchService: SearchService) { + } + + ngOnInit(): void { + this.searchLink = this.getSearchLink(); + this.removeParameters = this.getRemoveParams(); + } + + /** + * Calculates the parameters that should change if a given value for the given filter would be removed from the active filters + * @returns {Observable} The changed filter parameters + */ + getRemoveParams(): Observable { + return this.appliedFilters.pipe( + map((filters) => { + const field: string = Object.keys(filters).find((f) => f === this.key); + const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null; + return { + [field]: isNotEmpty(newValues) ? newValues : null, + page: 1 + }; + }) + ) + } + + /** + * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true + */ + private getSearchLink(): string { + if (this.inPlaceSearch) { + return './'; + } + return this.searchService.getSearchLink(); + } + + /** + * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved + * Strips authority operator from filter value + * e.g. 'test ,authority' => 'test' + * + * @param value + */ + normalizeFilterValue(value: string) { + // const pattern = /,[^,]*$/g; + const pattern = /,authority*$/g; + return value.replace(pattern, ''); + } +} diff --git a/src/app/+search-page/search-labels/search-labels.component.html b/src/app/+search-page/search-labels/search-labels.component.html index cac81e8717..6a668826da 100644 --- a/src/app/+search-page/search-labels/search-labels.component.html +++ b/src/app/+search-page/search-labels/search-labels.component.html @@ -1,13 +1,7 @@ diff --git a/src/app/+search-page/search-labels/search-labels.component.ts b/src/app/+search-page/search-labels/search-labels.component.ts index 104ed5b08b..5f95525bed 100644 --- a/src/app/+search-page/search-labels/search-labels.component.ts +++ b/src/app/+search-page/search-labels/search-labels.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { SearchService } from '../search-service/search.service'; import { Observable } from 'rxjs'; import { Params } from '@angular/router'; @@ -31,50 +31,7 @@ export class SearchLabelsComponent { * Initialize the instance variable */ constructor( - private searchService: SearchService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) { this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters(); } - - /** - * Calculates the parameters that should change if a given value for the given filter would be removed from the active filters - * @param {string} filterField The filter field parameter name from which the value should be removed - * @param {string} filterValue The value that is removed for this given filter field - * @returns {Observable} The changed filter parameters - */ - getRemoveParams(filterField: string, filterValue: string): Observable { - return this.appliedFilters.pipe( - map((filters) => { - const field: string = Object.keys(filters).find((f) => f === filterField); - const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== filterValue) : null; - return { - [field]: isNotEmpty(newValues) ? newValues : null, - page: 1 - }; - }) - ) - } - - /** - * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true - */ - public getSearchLink(): string { - if (this.inPlaceSearch) { - return './'; - } - return this.searchService.getSearchLink(); - } - - /** - * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved - * Strips authority operator from filter value - * e.g. 'test ,authority' => 'test' - * - * @param value - */ - normalizeFilterValue(value: string) { - // const pattern = /,[^,]*$/g; - const pattern = /,authority*$/g; - return value.replace(pattern, ''); - } } diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index b4d8c70f11..fc4c2dce09 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -7,7 +7,7 @@ @@ -15,12 +15,12 @@
+ [@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'"> + [ngClass]="{'active': !(isSidebarCollapsed$ | async)}">
diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index f23bff96f3..5e0a5ab9a2 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -91,6 +91,9 @@ export class SearchPageComponent implements OnInit { @Input() fixedFilter$: Observable; + searchLink: string; + isSidebarCollapsed$: Observable; + constructor(protected service: SearchService, protected sidebarService: SearchSidebarService, protected windowService: HostWindowService, @@ -107,6 +110,8 @@ export class SearchPageComponent implements OnInit { * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { + this.isSidebarCollapsed$ = this.isSidebarCollapsed(); + this.searchLink = this.getSearchLink(); this.searchOptions$ = this.getSearchOptions(); this.sub = this.searchOptions$.pipe( switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(observableOf(undefined))))) @@ -147,14 +152,14 @@ export class SearchPageComponent implements OnInit { * Check if the sidebar is collapsed * @returns {Observable} emits true if the sidebar is currently collapsed, false if it is expanded */ - public isSidebarCollapsed(): Observable { + private isSidebarCollapsed(): Observable { return this.sidebarService.isCollapsed; } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 65558eae17..297bfc8b74 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -32,6 +32,7 @@ import { SearchFacetSelectedOptionComponent } from './search-filters/search-filt import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component'; import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component'; import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component'; +import { SearchLabelComponent } from './search-labels/search-label/search-label.component'; const effects = [ SearchSidebarEffects @@ -49,6 +50,7 @@ const components = [ SearchFilterComponent, SearchFacetFilterComponent, SearchLabelsComponent, + SearchLabelComponent, SearchFacetFilterComponent, SearchFacetFilterWrapperComponent, SearchRangeFilterComponent, diff --git a/src/app/shared/items/switcher/item-type-switcher.component.html b/src/app/shared/items/switcher/item-type-switcher.component.html index 4965359495..f2ea5784fc 100644 --- a/src/app/shared/items/switcher/item-type-switcher.component.html +++ b/src/app/shared/items/switcher/item-type-switcher.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/items/switcher/item-type-switcher.component.ts b/src/app/shared/items/switcher/item-type-switcher.component.ts index 21a045b8f4..cd061bc1dd 100644 --- a/src/app/shared/items/switcher/item-type-switcher.component.ts +++ b/src/app/shared/items/switcher/item-type-switcher.component.ts @@ -32,6 +32,8 @@ export class ItemTypeSwitcherComponent implements OnInit { */ objectInjector: Injector; + component: any; + constructor(private injector: Injector) { } @@ -40,14 +42,14 @@ export class ItemTypeSwitcherComponent implements OnInit { providers: [{ provide: ITEM, useFactory: () => this.object, deps:[] }], parent: this.injector }); - + this.component = this.getComponent(); } /** * Fetch the component depending on the item's relationship type * @returns {string} */ - getComponent(): string { + private getComponent(): string { if (hasValue((this.object as any).representationType)) { const metadataRepresentation = this.object as MetadataRepresentation; return getComponentByItemType(metadataRepresentation.itemType, this.viewMode, metadataRepresentation.representationType); diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index 5ba889892a..a0878efa24 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -8,7 +8,7 @@ (pageSizeChange)="onPageSizeChange($event)" (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" - *ngIf="getViewMode()===viewModeEnum.List"> + *ngIf="(currentMode$ | async) === viewModeEnum.List"> + *ngIf="(currentMode$ | async) === viewModeEnum.Grid"> + *ngIf="(currentMode$ | async) === viewModeEnum.Detail"> diff --git a/src/app/shared/object-collection/object-collection.component.spec.ts b/src/app/shared/object-collection/object-collection.component.spec.ts index aed2b2598d..3b30666757 100644 --- a/src/app/shared/object-collection/object-collection.component.spec.ts +++ b/src/app/shared/object-collection/object-collection.component.spec.ts @@ -1,10 +1,8 @@ import { ObjectCollectionComponent } from './object-collection.component'; import { SetViewMode } from '../view-mode'; -import { element } from 'protractor'; import { By } from '@angular/platform-browser'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Config } from '../../../config/config.interface'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { RouterStub } from '../testing/router-stub'; @@ -38,14 +36,14 @@ describe('ObjectCollectionComponent', () => { })); it('should only show the grid component when the viewmode is set to grid', () => { - objectCollectionComponent.currentMode = SetViewMode.Grid; + objectCollectionComponent.currentMode$ = observableOf(SetViewMode.Grid); expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeDefined(); expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeNull(); }); it('should only show the list component when the viewmode is set to list', () => { - objectCollectionComponent.currentMode = SetViewMode.List; + objectCollectionComponent.currentMode$ = observableOf(SetViewMode.List); expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined(); expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull(); diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index ccc1de1f2f..526fc95781 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -11,7 +11,7 @@ import { import { ActivatedRoute, Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { filter, map, startWith } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { PageInfo } from '../../core/shared/page-info.model'; @@ -19,14 +19,14 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { ListableObject } from './shared/listable-object.model'; import { SetViewMode } from '../view-mode'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../empty.util'; @Component({ selector: 'ds-viewable-collection', styleUrls: ['./object-collection.component.scss'], templateUrl: './object-collection.component.html', }) -export class ObjectCollectionComponent implements OnChanges, OnInit { +export class ObjectCollectionComponent implements OnInit { @Input() objects: RemoteData; @Input() config?: PaginationComponentOptions; @@ -34,7 +34,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { @Input() hasBorder = false; @Input() hideGear = false; pageInfo: Observable; - private sub; /** * An event fired when the page is changed. * Event's payload equals to the newly selected page. @@ -61,25 +60,17 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { */ @Output() sortFieldChange: EventEmitter = new EventEmitter(); data: any = {}; - currentMode: SetViewMode = SetViewMode.List; + currentMode$: Observable; viewModeEnum = SetViewMode; - ngOnChanges(changes: SimpleChanges) { - if (changes.objects && !changes.objects.isFirstChange()) { - // this.pageInfo = this.objects.pageInfo; - } - } - ngOnInit(): void { - // this.pageInfo = this.objects.pageInfo; - - this.sub = this.route + this.currentMode$ = this.route .queryParams - .subscribe((params) => { - if (isNotEmpty(params.view)) { - this.currentMode = params.view; - } - }); + .pipe( + filter((params) => isNotEmpty(params.view)), + map((params) => params.view), + startWith(SetViewMode.List) + ); } /** @@ -96,15 +87,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { private router: Router) { } - getViewMode(): SetViewMode { - this.route.queryParams.pipe(map((params) => { - if (isNotEmpty(params.view) && hasValue(params.view)) { - this.currentMode = params.view; - } - })); - return this.currentMode; - } - onPageChange(event) { this.pageChange.emit(event); } diff --git a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html index 00a8ed2dc8..ef7254b97c 100644 --- a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html +++ b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts index 92b30f9ce7..2ca8069b16 100644 --- a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts +++ b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts @@ -26,6 +26,8 @@ export class WrapperDetailElementComponent implements OnInit { */ objectInjector: Injector; + detailElement: any; + /** * Initialize instance variables * @@ -42,13 +44,13 @@ export class WrapperDetailElementComponent implements OnInit { providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }], parent: this.injector }); - + this.detailElement = this.getDetailElement(); } /** * Return class name for the object to inject */ - getDetailElement(): string { + private getDetailElement(): string { const f: GenericConstructor = this.object.constructor as GenericConstructor; return rendersDSOType(f, SetViewMode.Detail); } diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html index c7e2f524f3..ecbf8f706e 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts index 0961dc96ee..2e19f2fdf9 100644 --- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts @@ -15,10 +15,12 @@ import { Metadata } from '../../../core/shared/metadata.utils'; export class SearchResultGridElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent { dso: K; + isCollapsed$: Observable; public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) { super(listableObject); this.dso = this.object.indexableObject; + this.isCollapsed$ = this.isCollapsed(); } /** @@ -41,7 +43,7 @@ export class SearchResultGridElementComponent, K exten return Metadata.firstValue([this.object.hitHighlights, this.dso.metadata], keyOrKeys); } - isCollapsed(): Observable { + private isCollapsed(): Observable { return this.truncatableService.isCollapsed(this.dso.id); } diff --git a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html index b613b16055..d6fd1cf9aa 100644 --- a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html +++ b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts index 84f9357b2d..0a7312484f 100644 --- a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts +++ b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts @@ -12,6 +12,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m export class WrapperGridElementComponent implements OnInit { @Input() object: ListableObject; objectInjector: Injector; + gridElement: any; constructor(private injector: Injector) { } @@ -21,7 +22,7 @@ export class WrapperGridElementComponent implements OnInit { providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }], parent: this.injector }); - + this.gridElement = this.getGridElement(); } getGridElement(): string { diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 227d375f2a..7017f3f48b 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -8,6 +8,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { TruncatableService } from '../../truncatable/truncatable.service'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { MetadataMap } from '../../../core/shared/metadata.models'; @Component({ selector: 'ds-search-result-list-element', @@ -16,6 +17,7 @@ import { Metadata } from '../../../core/shared/metadata.utils'; export class SearchResultListElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent { dso: K; + metadata: MetadataMap; public constructor(@Inject('objectElementProvider') public listable: ListableObject, protected truncatableService: TruncatableService) { super(listable); diff --git a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html index d5cfebdfa5..db87596f31 100644 --- a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html +++ b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts index 17e6f0fd85..29b1364a75 100644 --- a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts +++ b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts @@ -13,6 +13,7 @@ export class WrapperListElementComponent implements OnInit { @Input() object: ListableObject; @Input() index: number; objectInjector: Injector; + listElement: any; constructor(private injector: Injector) {} @@ -24,9 +25,10 @@ export class WrapperListElementComponent implements OnInit { ], parent: this.injector }); + this.listElement = this.getListElement(); } - getListElement(): string { + private getListElement(): string { const f: GenericConstructor = this.object.constructor as GenericConstructor; return rendersDSOType(f, SetViewMode.List); } From bb76015aa12886337fb65154630d7fd925b8d7f7 Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 18 Jun 2019 08:58:35 +0200 Subject: [PATCH 22/60] Solved issue with non-existing search pages --- .../create-collection-page.component.spec.ts | 2 +- .../create-collection-page.component.ts | 2 +- .../create-community-page.component.spec.ts | 2 +- .../create-community-page.component.ts | 2 +- .../delete-community-page.component.spec.ts | 2 +- .../my-dspace-configuration.service.ts | 2 +- .../my-dspace-page.component.spec.ts | 2 +- .../filtered-search-page.component.ts | 7 +++- .../filtered-search-page.guard.ts | 37 +++++++++++++++++-- .../search-filter/search-filter.service.ts | 2 +- .../search-fixed-filter.service.spec.ts | 2 +- .../search-fixed-filter.service.ts | 4 +- .../search-range-filter.component.spec.ts | 2 +- .../search-range-filter.component.ts | 2 +- .../search-page-routing.module.ts | 9 ++++- .../search-page.component.spec.ts | 2 +- src/app/+search-page/search-page.component.ts | 10 ++--- .../search-configuration.service.ts | 2 +- .../search-service/search.service.spec.ts | 2 +- .../search-service/search.service.ts | 2 +- src/app/app.component.spec.ts | 4 +- src/app/app.component.ts | 4 +- src/app/core/auth/auth.service.spec.ts | 6 +-- src/app/core/auth/auth.service.ts | 4 +- src/app/core/core.effects.ts | 2 +- src/app/core/core.module.ts | 8 ++-- src/app/core/core.reducers.ts | 2 +- .../{shared => core}/services/api.service.ts | 0 .../services/client-cookie.service.ts | 0 .../services/cookie.service.spec.ts | 0 .../services/cookie.service.ts | 0 .../services/route.actions.ts | 0 .../services/route.effects.ts | 0 .../services/route.reducer.ts | 0 .../services/route.service.spec.ts | 4 +- .../services/route.service.ts | 10 ++--- .../services/server-cookie.service.ts | 0 .../services/server-response.service.ts | 0 .../services/window.service.ts | 0 .../pagenotfound/pagenotfound.component.ts | 2 +- .../create-comcol-page.component.spec.ts | 2 +- .../create-comcol-page.component.ts | 2 +- src/app/submission/submission.module.ts | 2 +- src/app/submission/submission.service.spec.ts | 2 +- src/app/submission/submission.service.ts | 2 +- src/modules/app/browser-app.module.ts | 4 +- src/modules/app/server-app.module.ts | 4 +- 47 files changed, 99 insertions(+), 62 deletions(-) rename src/app/{shared => core}/services/api.service.ts (100%) rename src/app/{shared => core}/services/client-cookie.service.ts (100%) rename src/app/{shared => core}/services/cookie.service.spec.ts (100%) rename src/app/{shared => core}/services/cookie.service.ts (100%) rename src/app/{shared => core}/services/route.actions.ts (100%) rename src/app/{shared => core}/services/route.effects.ts (100%) rename src/app/{shared => core}/services/route.reducer.ts (100%) rename src/app/{shared => core}/services/route.service.spec.ts (97%) rename src/app/{shared => core}/services/route.service.ts (95%) rename src/app/{shared => core}/services/server-cookie.service.ts (100%) rename src/app/{shared => core}/services/server-response.service.ts (100%) rename src/app/{shared => core}/services/window.service.ts (100%) diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts index 29350a83e0..e223b11c65 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts index 94229b4932..2cab36d285 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { Router } from '@angular/router'; import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; import { Collection } from '../../core/shared/collection.model'; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts index dba15dbe88..dead5a5c3b 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.ts b/src/app/+community-page/create-community-page/create-community-page.component.ts index 828d8338af..fd5f18442a 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { Router } from '@angular/router'; import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts index f18c4fb1f1..c23df93976 100644 --- a/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { of as observableOf } from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.ts index 705ec897f8..39c7574407 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.ts @@ -8,7 +8,7 @@ import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value- import { RoleService } from '../core/roles/role.service'; import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model'; import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service'; diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts index 9658814a6a..d31d724b9e 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts @@ -17,7 +17,7 @@ import { HostWindowService } from '../shared/host-window.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { RemoteData } from '../core/data/remote-data'; import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { routeServiceStub } from '../shared/testing/route-service-stub'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { SearchService } from '../+search-page/search-service/search.service'; diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts index 66c619b823..85d521ee15 100644 --- a/src/app/+search-page/filtered-search-page.component.ts +++ b/src/app/+search-page/filtered-search-page.component.ts @@ -4,12 +4,14 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { Observable } from 'rxjs'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { map } from 'rxjs/operators'; +import { isEmpty, isNotEmpty } from '../shared/empty.util'; +import { ActivatedRoute } from '@angular/router'; /** * This component renders a simple item page. @@ -53,6 +55,9 @@ export class FilteredSearchPageComponent extends SearchPageComponent implements * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { + if (isEmpty(this.fixedFilter$)) { + this.fixedFilter$ = this.routeService.getRouteParameterValue('filter'); + } super.ngOnInit(); } diff --git a/src/app/+search-page/filtered-search-page.guard.ts b/src/app/+search-page/filtered-search-page.guard.ts index 6d41d4965d..e28dadec71 100644 --- a/src/app/+search-page/filtered-search-page.guard.ts +++ b/src/app/+search-page/filtered-search-page.guard.ts @@ -1,6 +1,16 @@ import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { + ActivatedRouteSnapshot, + CanActivate, + NavigationEnd, + Router, + RouterStateSnapshot +} from '@angular/router'; import { Observable } from 'rxjs'; +import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service'; +import { map, take, tap, filter } from 'rxjs/operators'; +import { isEmpty, isNotEmpty } from '../shared/empty.util'; +import { Location } from '@angular/common'; @Injectable() /** @@ -9,14 +19,33 @@ import { Observable } from 'rxjs'; * - filter: The current filter stored in route.params */ export class FilteredSearchPageGuard implements CanActivate { + constructor(private service: SearchFixedFilterService, private router: Router, private location: Location) { + } + canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { - const filter = route.params.filter; + route.params = Object.assign({}, route.params, { filter: route.params.filter.toLowerCase() }); + const filterName = route.params.filter; - const newTitle = filter + '.search.title'; + const newTitle = filterName + '.search.title'; route.data = { title: newTitle }; - return true; + + return this.service.getQueryByFilterName(filterName).pipe( + tap((query) => { + if (isEmpty(query)) { + this.router.navigateByUrl('/404', { skipLocationChange: true }); + this.router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(() => this.location.replaceState(state.url)); + } + } + ), + map((query) => isNotEmpty(query)) + ); } } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 6024ad7249..8482838101 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -14,7 +14,7 @@ import { } from './search-filter.actions'; import { hasValue, isNotEmpty, } from '../../../shared/empty.util'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; -import { RouteService } from '../../../shared/services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SearchFixedFilterService } from './search-fixed-filter.service'; diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts index 3f6c2ef133..a201d37d48 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts @@ -1,5 +1,5 @@ import { SearchFixedFilterService } from './search-fixed-filter.service'; -import { RouteService } from '../../../shared/services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { RequestService } from '../../../core/data/request.service'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts index 0f17b508c9..85f637ce32 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts @@ -9,7 +9,6 @@ import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { FilteredDiscoveryPageResponseParsingService } from '../../../core/data/filtered-discovery-page-response-parsing.service'; import { hasValue } from '../../../shared/empty.util'; import { configureRequest, getResponseFromEntry } from '../../../core/shared/operators'; -import { RouteService } from '../../../shared/services/route.service'; import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models'; /** @@ -19,8 +18,7 @@ import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.mod export class SearchFixedFilterService { private queryByFilterPath = 'filtered-discovery-pages'; - constructor(private routeService: RouteService, - protected requestService: RequestService, + constructor(protected requestService: RequestService, private halService: HALEndpointService) { } diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index 119f3f92a9..2b69fe7f55 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -16,7 +16,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub'; import { Router } from '@angular/router'; import { PageInfo } from '../../../../core/shared/page-info.model'; import { SearchRangeFilterComponent } from './search-range-filter.component'; -import { RouteService } from '../../../../shared/services/route.service'; +import { RouteService } from '../../../../core/services/route.service'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service-stub'; diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 95d7441184..5ac59b65f8 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -14,7 +14,7 @@ import { FILTER_CONFIG, IN_PLACE_SEARCH, SearchFilterService } from '../search-f import { SearchService } from '../../../search-service/search.service'; import { Router } from '@angular/router'; import * as moment from 'moment'; -import { RouteService } from '../../../../shared/services/route.service'; +import { RouteService } from '../../../../core/services/route.service'; import { hasValue } from '../../../../shared/empty.util'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index c3cf4e1343..e3a91c6f69 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -9,8 +9,13 @@ import { FilteredSearchPageGuard } from './filtered-search-page.guard'; imports: [ RouterModule.forChild([ { path: '', component: SearchPageComponent, data: { title: 'search.title' } }, - { path: ':filter', component: FilteredSearchPageComponent, canActivate: [FilteredSearchPageGuard]} + { + path: ':filter', + component: FilteredSearchPageComponent, + canActivate: [FilteredSearchPageGuard], + } ]) ] }) -export class SearchPageRoutingModule { } +export class SearchPageRoutingModule { +} diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index 88c7c693d3..2bc3d4071d 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -21,7 +21,7 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte import { SearchConfigurationService } from './search-service/search-configuration.service'; import { RemoteData } from '../core/data/remote-data'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service'; diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 5e0a5ab9a2..8fe38eaebb 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -13,7 +13,7 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { getSucceededRemoteData } from '../core/shared/operators'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; export const SEARCH_ROUTE = '/search'; @@ -114,16 +114,16 @@ export class SearchPageComponent implements OnInit { this.searchLink = this.getSearchLink(); this.searchOptions$ = this.getSearchOptions(); this.sub = this.searchOptions$.pipe( - switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(observableOf(undefined))))) + switchMap((options) => this.service.search(options).pipe( + getSucceededRemoteData(), + startWith(undefined) + ))) .subscribe((results) => { this.resultsRD$.next(results); }); this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe( switchMap((scopeId) => this.service.getScopes(scopeId)) ); - if (!isNotEmpty(this.fixedFilter$)) { - this.fixedFilter$ = this.routeService.getRouteParameterValue('filter'); - } } /** diff --git a/src/app/+search-page/search-service/search-configuration.service.ts b/src/app/+search-page/search-service/search-configuration.service.ts index 14fcdd8d60..fa43b27e66 100644 --- a/src/app/+search-page/search-service/search-configuration.service.ts +++ b/src/app/+search-page/search-service/search-configuration.service.ts @@ -14,7 +14,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SearchOptions } from '../search-options.model'; import { PaginatedSearchOptions } from '../paginated-search-options.model'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { RemoteData } from '../../core/data/remote-data'; import { getSucceededRemoteData } from '../../core/shared/operators'; diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts index 9ec5bc35f2..b505504870 100644 --- a/src/app/+search-page/search-service/search.service.spec.ts +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -26,7 +26,7 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { map } from 'rxjs/operators'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { routeServiceStub } from '../../shared/testing/route-service-stub'; @Component({ template: '' }) diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 52be0417a8..6685e7b715 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -42,7 +42,7 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { ResourceType } from '../../core/shared/resource-type'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; /** * Service that performs all general actions that have to do with the search page diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index bd2d832c67..b7b34f9c55 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -26,7 +26,7 @@ import { HostWindowResizeAction } from './shared/host-window.actions'; import { MetadataService } from './core/metadata/metadata.service'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; -import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; import { MockMetadataService } from './shared/mocks/mock-metadata-service'; @@ -41,7 +41,7 @@ import { MenuServiceStub } from './shared/testing/menu-service-stub'; import { HostWindowService } from './shared/host-window.service'; import { HostWindowServiceStub } from './shared/testing/host-window-service-stub'; import { ActivatedRoute, Router } from '@angular/router'; -import { RouteService } from './shared/services/route.service'; +import { RouteService } from './core/services/route.service'; import { MockActivatedRoute } from './shared/mocks/mock-active-router'; import { MockRouter } from './shared/mocks/mock-router'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 37cc791558..836c20208d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -19,11 +19,11 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config'; import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/host-window.reducer'; -import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { isAuthenticated } from './core/auth/selectors'; import { AuthService } from './core/auth/auth.service'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; -import { RouteService } from './shared/services/route.service'; +import { RouteService } from './core/services/route.service'; import variables from '../styles/_exposed_variables.scss'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { MenuService } from './shared/menu/menu.service'; diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index e766a45e48..ab2e6fd86b 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -7,12 +7,12 @@ import { REQUEST } from '@nguniversal/express-engine/tokens'; import { of as observableOf } from 'rxjs'; import { authReducer, AuthState } from './auth.reducer'; -import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { AuthService } from './auth.service'; import { RouterStub } from '../../shared/testing/router-stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; -import { CookieService } from '../../shared/services/cookie.service'; +import { CookieService } from '../services/cookie.service'; import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service-stub'; import { AuthRequestService } from './auth-request.service'; import { AuthStatus } from './models/auth-status.model'; @@ -20,7 +20,7 @@ import { AuthTokenInfo } from './models/auth-token-info.model'; import { EPerson } from '../eperson/models/eperson.model'; import { EPersonMock } from '../../shared/testing/eperson-mock'; import { AppState } from '../../app.reducer'; -import { ClientCookieService } from '../../shared/services/client-cookie.service'; +import { ClientCookieService } from '../services/client-cookie.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index a01768e687..08c94b02f2 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -15,11 +15,11 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model'; import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; -import { CookieService } from '../../shared/services/cookie.service'; +import { CookieService } from '../services/cookie.service'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { AppState, routerStateSelector } from '../../app.reducer'; import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; -import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 9ade23e6c5..f657b5d449 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -6,7 +6,7 @@ import { AuthEffects } from './auth/auth.effects'; import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects'; import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects'; -import { RouteEffects } from '../shared/services/route.effects'; +import { RouteEffects } from './services/route.effects'; export const coreEffects = [ RequestEffects, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6550435aa3..31d1da1ede 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -15,7 +15,7 @@ import { coreReducers } from './core.reducers'; import { isNotEmpty } from '../shared/empty.util'; -import { ApiService } from '../shared/services/api.service'; +import { ApiService } from './services/api.service'; import { BrowseEntriesResponseParsingService } from './data/browse-entries-response-parsing.service'; import { CollectionDataService } from './data/collection-data.service'; import { CommunityDataService } from './data/community-data.service'; @@ -35,12 +35,12 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; import { RequestService } from './data/request.service'; import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service'; -import { ServerResponseService } from '../shared/services/server-response.service'; -import { NativeWindowFactory, NativeWindowService } from '../shared/services/window.service'; +import { ServerResponseService } from './services/server-response.service'; +import { NativeWindowFactory, NativeWindowService } from './services/window.service'; import { BrowseService } from './browse/browse.service'; import { BrowseResponseParsingService } from './data/browse-response-parsing.service'; import { ConfigResponseParsingService } from './config/config-response-parsing.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from './services/route.service'; import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service'; import { SubmissionFormsConfigService } from './config/submission-forms-config.service'; import { SubmissionSectionsConfigService } from './config/submission-sections-config.service'; diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index 7aecb91a7a..4fcf36f9cc 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -13,7 +13,7 @@ import { objectUpdatesReducer, ObjectUpdatesState } from './data/object-updates/object-updates.reducer'; -import { routeReducer, RouteState } from '../shared/services/route.reducer'; +import { routeReducer, RouteState } from './services/route.reducer'; export interface CoreState { 'cache/object': ObjectCacheState, diff --git a/src/app/shared/services/api.service.ts b/src/app/core/services/api.service.ts similarity index 100% rename from src/app/shared/services/api.service.ts rename to src/app/core/services/api.service.ts diff --git a/src/app/shared/services/client-cookie.service.ts b/src/app/core/services/client-cookie.service.ts similarity index 100% rename from src/app/shared/services/client-cookie.service.ts rename to src/app/core/services/client-cookie.service.ts diff --git a/src/app/shared/services/cookie.service.spec.ts b/src/app/core/services/cookie.service.spec.ts similarity index 100% rename from src/app/shared/services/cookie.service.spec.ts rename to src/app/core/services/cookie.service.spec.ts diff --git a/src/app/shared/services/cookie.service.ts b/src/app/core/services/cookie.service.ts similarity index 100% rename from src/app/shared/services/cookie.service.ts rename to src/app/core/services/cookie.service.ts diff --git a/src/app/shared/services/route.actions.ts b/src/app/core/services/route.actions.ts similarity index 100% rename from src/app/shared/services/route.actions.ts rename to src/app/core/services/route.actions.ts diff --git a/src/app/shared/services/route.effects.ts b/src/app/core/services/route.effects.ts similarity index 100% rename from src/app/shared/services/route.effects.ts rename to src/app/core/services/route.effects.ts diff --git a/src/app/shared/services/route.reducer.ts b/src/app/core/services/route.reducer.ts similarity index 100% rename from src/app/shared/services/route.reducer.ts rename to src/app/core/services/route.reducer.ts diff --git a/src/app/shared/services/route.service.spec.ts b/src/app/core/services/route.service.spec.ts similarity index 97% rename from src/app/shared/services/route.service.spec.ts rename to src/app/core/services/route.service.spec.ts index c6003521a7..ae31f28384 100644 --- a/src/app/shared/services/route.service.spec.ts +++ b/src/app/core/services/route.service.spec.ts @@ -6,9 +6,9 @@ import { Store } from '@ngrx/store'; import { getTestScheduler, hot } from 'jasmine-marbles'; import { RouteService } from './route.service'; -import { MockRouter } from '../mocks/mock-router'; +import { MockRouter } from '../../shared/mocks/mock-router'; import { TestScheduler } from 'rxjs/testing'; -import { AddUrlToHistoryAction } from '../history/history.actions'; +import { AddUrlToHistoryAction } from '../../shared/history/history.actions'; describe('RouteService', () => { let scheduler: TestScheduler; diff --git a/src/app/shared/services/route.service.ts b/src/app/core/services/route.service.ts similarity index 95% rename from src/app/shared/services/route.service.ts rename to src/app/core/services/route.service.ts index dc626484c1..65aa858945 100644 --- a/src/app/shared/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -12,12 +12,12 @@ import { combineLatest, Observable } from 'rxjs'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { isEqual } from 'lodash'; -import { AddUrlToHistoryAction } from '../history/history.actions'; -import { historySelector } from '../history/selectors'; +import { AddUrlToHistoryAction } from '../../shared/history/history.actions'; +import { historySelector } from '../../shared/history/selectors'; import { SetParametersAction, SetQueryParametersAction } from './route.actions'; -import { CoreState } from '../../core/core.reducers'; -import { hasValue } from '../empty.util'; -import { coreSelector } from '../../core/core.selectors'; +import { CoreState } from '../core.reducers'; +import { hasValue } from '../../shared/empty.util'; +import { coreSelector } from '../core.selectors'; /** * Selector to select all route parameters from the store diff --git a/src/app/shared/services/server-cookie.service.ts b/src/app/core/services/server-cookie.service.ts similarity index 100% rename from src/app/shared/services/server-cookie.service.ts rename to src/app/core/services/server-cookie.service.ts diff --git a/src/app/shared/services/server-response.service.ts b/src/app/core/services/server-response.service.ts similarity index 100% rename from src/app/shared/services/server-response.service.ts rename to src/app/core/services/server-response.service.ts diff --git a/src/app/shared/services/window.service.ts b/src/app/core/services/window.service.ts similarity index 100% rename from src/app/shared/services/window.service.ts rename to src/app/core/services/window.service.ts diff --git a/src/app/pagenotfound/pagenotfound.component.ts b/src/app/pagenotfound/pagenotfound.component.ts index 6e173b4139..b11de58269 100644 --- a/src/app/pagenotfound/pagenotfound.component.ts +++ b/src/app/pagenotfound/pagenotfound.component.ts @@ -1,4 +1,4 @@ -import { ServerResponseService } from '../shared/services/server-response.service'; +import { ServerResponseService } from '../core/services/server-response.service'; import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { AuthService } from '../core/auth/auth.service'; diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts index 4dad4a703f..c53c45fbe9 100644 --- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CommunityDataService } from '../../../core/data/community-data.service'; -import { RouteService } from '../../services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts index c9fcfecb97..e07f2a5a0a 100644 --- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Community } from '../../../core/shared/community.model'; import { CommunityDataService } from '../../../core/data/community-data.service'; import { Observable } from 'rxjs'; -import { RouteService } from '../../services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../../core/data/remote-data'; import { isNotEmpty, isNotUndefined } from '../../empty.util'; diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index e6c24226e2..82f57ea970 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -31,7 +31,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' @NgModule({ imports: [ CommonModule, - CoreModule, + CoreModule.forRoot(), SharedModule, StoreModule.forFeature('submission', submissionReducers, {}), EffectsModule.forFeature(submissionEffects), diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index d764f09538..80ad3b606a 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -12,7 +12,7 @@ import { MockRouter } from '../shared/mocks/mock-router'; import { SubmissionService } from './submission.service'; import { submissionReducers } from './submission.reducers'; import { SubmissionRestService } from '../core/submission/submission-rest.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SubmissionRestServiceStub } from '../shared/testing/submission-rest-service-stub'; import { MockActivatedRoute } from '../shared/mocks/mock-active-router'; import { GLOBAL_CONFIG } from '../../config'; diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 82185a8eae..36aedaaab6 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -35,7 +35,7 @@ import { SubmissionRestService } from '../core/submission/submission-rest.servic import { SectionDataObject } from './sections/models/section-data.model'; import { SubmissionScopeType } from '../core/submission/submission-scope-type'; import { SubmissionObject } from '../core/submission/models/submission-object.model'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SectionsType } from './sections/sections-type'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { SubmissionDefinitionsModel } from '../core/config/models/config-submission-definitions.model'; diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index b20894880b..7ff70457bb 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -15,8 +15,8 @@ import { AppComponent } from '../../app/app.component'; import { AppModule } from '../../app/app.module'; import { DSpaceBrowserTransferStateModule } from '../transfer-state/dspace-browser-transfer-state.module'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; -import { ClientCookieService } from '../../app/shared/services/client-cookie.service'; -import { CookieService } from '../../app/shared/services/cookie.service'; +import { ClientCookieService } from '../../app/core/services/client-cookie.service'; +import { CookieService } from '../../app/core/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { Angulartics2Module } from 'angulartics2'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index d809d3cced..bd3379c8de 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -13,8 +13,8 @@ import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { TranslateUniversalLoader } from '../translate-universal-loader'; -import { CookieService } from '../../app/shared/services/cookie.service'; -import { ServerCookieService } from '../../app/shared/services/server-cookie.service'; +import { CookieService } from '../../app/core/services/cookie.service'; +import { ServerCookieService } from '../../app/core/services/server-cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { ServerAuthService } from '../../app/core/auth/server-auth.service'; From 14d7437da927d6087a98721660e9800e23c1f4f4 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Jun 2019 17:47:44 +0200 Subject: [PATCH 23/60] 63184: Edit-Relationships left/right Item refactoring --- .../item-relationships.component.ts | 68 +++++++------ .../shared/item-relationships-utils.ts | 17 +++- src/app/core/data/relationship.service.ts | 98 +++++++++++++++---- 3 files changed, 133 insertions(+), 50 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 3e74794866..36b2e212ba 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -21,6 +21,7 @@ import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { getSucceededRemoteData } from '../../../core/shared/operators'; import { RequestService } from '../../../core/data/request.service'; import { Subscription } from 'rxjs/internal/Subscription'; +import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils'; @Component({ selector: 'ds-item-relationships', @@ -94,7 +95,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl } /** - * Resolve the currently selected related items back to relationships and send a delete request + * Resolve the currently selected related items back to relationships and send a delete request for each of the relationships found * Make sure the lists are refreshed afterwards and notifications are sent for success and errors */ public submit(): void { @@ -105,42 +106,51 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field.uuid) as string[]), isNotEmptyOperator() ); - const allRelationshipsAndRemovedItemIds$ = observableCombineLatest( - this.relationshipService.getItemRelationshipsArray(this.item), - removedItemIds$ - ); - // Get all IDs of the relationships that should be removed - const removedRelationshipIds$ = allRelationshipsAndRemovedItemIds$.pipe( - map(([relationships, itemIds]) => - relationships - .filter((relationship: Relationship) => itemIds.indexOf(relationship.leftId) > -1 || itemIds.indexOf(relationship.rightId) > -1) - .map((relationship: Relationship) => relationship.id)) + // Get all the relationships that should be removed + const removedRelationships$ = removedItemIds$.pipe( + getRelationsByRelatedItemIds(this.item, this.relationshipService) ); // Request a delete for every relationship found in the observable created above - removedRelationshipIds$.pipe( + removedRelationships$.pipe( take(1), + map((removedRelationships: Relationship[]) => removedRelationships.map((rel: Relationship) => rel.id)), switchMap((removedIds: string[]) => observableZip(...removedIds.map((uuid: string) => this.relationshipService.deleteRelationship(uuid)))) ).subscribe((responses: RestResponse[]) => { - const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); - const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful); - - // Display an error notification for each failed request - failedResponses.forEach((response: ErrorResponse) => { - this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); - }); - if (successfulResponses.length > 0) { - // Remove the item's cache to make sure the lists are reloaded with the newest values - this.objectCache.remove(this.item.self); - this.requestService.removeByHrefSubstring(this.item.self); - // Send a notification that the removal was successful - this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); - } - // Reset the state of editing relationships - this.initializeOriginalFields(); - this.initializeUpdates(); + this.displayNotifications(responses); + this.reset(); }); } + /** + * Display notifications + * - Error notification for each failed response with their message + * - Success notification in case there's at least one successful response + * @param responses + */ + displayNotifications(responses: RestResponse[]) { + const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); + const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful); + + failedResponses.forEach((response: ErrorResponse) => { + this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); + }); + if (successfulResponses.length > 0) { + // Remove the item's cache to make sure the lists are reloaded with the newest values + this.objectCache.remove(this.item.self); + this.requestService.removeByHrefSubstring(this.item.self); + // Send a notification that the removal was successful + this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); + } + } + + /** + * Reset the state of editing relationships + */ + reset() { + this.initializeOriginalFields(); + this.initializeUpdates(); + } + /** * Sends all initial values of this item to the object updates service */ diff --git a/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts b/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts index 91f7c52bb8..eaea3d5d3e 100644 --- a/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts +++ b/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts @@ -7,11 +7,12 @@ import { hasValue } from '../../../../shared/empty.util'; import { Observable } from 'rxjs/internal/Observable'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; -import { distinctUntilChanged, flatMap, map } from 'rxjs/operators'; +import { distinctUntilChanged, filter, flatMap, map, tap } from 'rxjs/operators'; import { of as observableOf, zip as observableZip, combineLatest as observableCombineLatest } from 'rxjs'; import { ItemDataService } from '../../../../core/data/item-data.service'; import { Item } from '../../../../core/shared/item.model'; import { RemoteData } from '../../../../core/data/remote-data'; +import { RelationshipService } from '../../../../core/data/relationship.service'; /** * Operator for comparing arrays using a mapping function @@ -120,3 +121,17 @@ export const relationsToRepresentations = (parentId: string, itemType: string, m ) ) ); + +/** + * Operator for fetching an item's relationships, but filtered by related item IDs (essentially performing a reverse lookup) + * Only relationships where leftItem or rightItem's ID is present in the list provided will be returned + * @param item + * @param relationshipService + */ +export const getRelationsByRelatedItemIds = (item: Item, relationshipService: RelationshipService) => + (source: Observable): Observable => + source.pipe( + flatMap((relatedItemIds: string[]) => relationshipService.getItemResolvedRelatedItemsAndRelationships(item).pipe( + map(([leftItems, rightItems, rels]) => rels.filter((rel: Relationship, index: number) => relatedItemIds.indexOf(leftItems[index].uuid) > -1 || relatedItemIds.indexOf(rightItems[index].uuid) > -1)) + )) + ); diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 000faaf2c3..fca5074a88 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -3,7 +3,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { hasValue, hasValueOperator, isNotEmptyOperator } from '../../shared/empty.util'; -import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, @@ -70,20 +70,35 @@ export class RelationshipService { * @param item */ getItemResolvedRelsAndTypes(item: Item): Observable<[Relationship[], RelationshipType[]]> { - const relationships$ = this.getItemRelationshipsArray(item); - - const relationshipTypes$ = relationships$.pipe( - flatMap((rels: Relationship[]) => - observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe( - map(([...arr]: Array>) => arr.map((d: RemoteData) => d.payload).filter((type) => hasValue(type))) - ) - ), - distinctUntilChanged(compareArraysUsingIds()) - ); - return observableCombineLatest( - relationships$, - relationshipTypes$ + this.getItemRelationshipsArray(item), + this.getItemRelationshipTypesArray(item) + ); + } + + /** + * Get a combined observable containing an array of all the item's relationship's left- and right-side items, as well as an array of the relationships their types + * This is used for easier access of a relationship's type and left and right items because they exist as observables + * @param item + */ + getItemResolvedRelatedItemsAndTypes(item: Item): Observable<[Item[], Item[], RelationshipType[]]> { + return observableCombineLatest( + this.getItemLeftRelatedItemArray(item), + this.getItemRightRelatedItemArray(item), + this.getItemRelationshipTypesArray(item) + ); + } + + /** + * Get a combined observable containing an array of all the item's relationship's left- and right-side items, as well as an array of the relationships themselves + * This is used for easier access of the relationship and their left and right items because they exist as observables + * @param item + */ + getItemResolvedRelatedItemsAndRelationships(item: Item): Observable<[Item[], Item[], Relationship[]]> { + return observableCombineLatest( + this.getItemLeftRelatedItemArray(item), + this.getItemRightRelatedItemArray(item), + this.getItemRelationshipsArray(item) ); } @@ -101,17 +116,60 @@ export class RelationshipService { ); } + /** + * Get an item their relationship types in the form of an array + * @param item + */ + getItemRelationshipTypesArray(item: Item): Observable { + return this.getItemRelationshipsArray(item).pipe( + flatMap((rels: Relationship[]) => + observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe( + map(([...arr]: Array>) => arr.map((d: RemoteData) => d.payload).filter((type) => hasValue(type))), + filter((arr) => arr.length === rels.length) + ) + ), + distinctUntilChanged(compareArraysUsingIds()) + ); + } + + /** + * Get an item his relationship's left-side related items in the form of an array + * @param item + */ + getItemLeftRelatedItemArray(item: Item): Observable { + return this.getItemRelationshipsArray(item).pipe( + flatMap((rels: Relationship[]) => observableZip(...rels.map((rel: Relationship) => rel.leftItem)).pipe( + map(([...arr]: Array>) => arr.map((rd: RemoteData) => rd.payload).filter((i) => hasValue(i))), + filter((arr) => arr.length === rels.length) + )), + distinctUntilChanged(compareArraysUsingIds()) + ); + } + + /** + * Get an item his relationship's right-side related items in the form of an array + * @param item + */ + getItemRightRelatedItemArray(item: Item): Observable { + return this.getItemRelationshipsArray(item).pipe( + flatMap((rels: Relationship[]) => observableZip(...rels.map((rel: Relationship) => rel.rightItem)).pipe( + map(([...arr]: Array>) => arr.map((rd: RemoteData) => rd.payload).filter((i) => hasValue(i))), + filter((arr) => arr.length === rels.length) + )), + distinctUntilChanged(compareArraysUsingIds()) + ); + } + /** * Get an array of an item their unique relationship type's labels * The array doesn't contain any duplicate labels * @param item */ getItemRelationshipLabels(item: Item): Observable { - return this.getItemResolvedRelsAndTypes(item).pipe( - map(([relsCurrentPage, relTypesCurrentPage]) => { + return this.getItemResolvedRelatedItemsAndTypes(item).pipe( + map(([leftItems, rightItems, relTypesCurrentPage]) => { return relTypesCurrentPage.map((type, index) => { - const relationship = relsCurrentPage[index]; - if (relationship.leftId === item.uuid) { + if (leftItems[index].uuid === item.uuid) { return type.leftLabel; } else { return type.rightLabel; @@ -128,7 +186,7 @@ export class RelationshipService { */ getRelatedItems(item: Item): Observable { return this.getItemRelationshipsArray(item).pipe( - relationsToItems(item.uuid, this.itemService) + relationsToItems(item.uuid) ); } @@ -141,7 +199,7 @@ export class RelationshipService { getRelatedItemsByLabel(item: Item, label: string): Observable { return this.getItemResolvedRelsAndTypes(item).pipe( filterRelationsByTypeLabel(label), - relationsToItems(item.uuid, this.itemService) + relationsToItems(item.uuid) ); } From 2f6c0fb1264da0a2c4c5791ba06dbaab8230e5d1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Jun 2019 10:19:53 +0200 Subject: [PATCH 24/60] 63184: Fixed test cases --- .../item-relationships.component.spec.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts index 2439eb4c63..1d3d59201b 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts @@ -15,7 +15,7 @@ import { GLOBAL_CONFIG } from '../../../../config'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; import { ResourceType } from '../../../core/shared/resource-type'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; import { PaginatedList } from '../../../core/data/paginated-list'; @@ -78,16 +78,12 @@ describe('ItemRelationshipsComponent', () => { self: url + '/2', id: '2', uuid: '2', - leftId: 'author1', - rightId: 'publication', relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) }), Object.assign(new Relationship(), { self: url + '/3', id: '3', uuid: '3', - leftId: 'author2', - rightId: 'publication', relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) }) ]; @@ -109,6 +105,11 @@ describe('ItemRelationshipsComponent', () => { uuid: 'author2' }); + relationships[0].leftItem = observableOf(new RemoteData(false, false, true, undefined, author1)); + relationships[0].rightItem = observableOf(new RemoteData(false, false, true, undefined, item)); + relationships[1].leftItem = observableOf(new RemoteData(false, false, true, undefined, author2)); + relationships[1].rightItem = observableOf(new RemoteData(false, false, true, undefined, item)); + fieldUpdate1 = { field: author1, changeType: undefined @@ -155,7 +156,8 @@ describe('ItemRelationshipsComponent', () => { getRelatedItems: observableOf([author1, author2]), getRelatedItemsByLabel: observableOf([author1, author2]), getItemRelationshipsArray: observableOf(relationships), - deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')) + deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')), + getItemResolvedRelatedItemsAndRelationships: observableCombineLatest(observableOf([author1, author2]), observableOf([item, item]), observableOf(relationships)) } ); From 417f79ab6a8108f6f18745cf25cbff3c8625d1db Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Jun 2019 11:55:10 +0200 Subject: [PATCH 25/60] 63184: Relationship list refreshing fix - Re-initializing itemUpdateSubscription --- .../item-relationships/item-relationships.component.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 36b2e212ba..087dea656b 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -66,8 +66,13 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl ngOnInit(): void { super.ngOnInit(); this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item); + this.initializeItemUpdate(); + } - // Update the item (and view) when it's removed in the request cache + /** + * Update the item (and view) when it's removed in the request cache + */ + public initializeItemUpdate(): void { this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe( filter((exists: boolean) => !exists), switchMap(() => this.itemService.findById(this.item.uuid)), @@ -144,11 +149,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl } /** - * Reset the state of editing relationships + * Re-initialize fields and subscriptions */ reset() { this.initializeOriginalFields(); this.initializeUpdates(); + this.initializeItemUpdate(); } /** From d734ed108ef960283672ac27dd0a0fb7f2edf946 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 4 Jul 2019 17:52:14 +0200 Subject: [PATCH 26/60] 63469: Intermediate commit --- .../item-relationships.component.ts | 4 --- src/app/core/data/relationship.service.ts | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts index 087dea656b..e8f34bc70e 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -140,10 +140,6 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); }); if (successfulResponses.length > 0) { - // Remove the item's cache to make sure the lists are reloaded with the newest values - this.objectCache.remove(this.item.self); - this.requestService.removeByHrefSubstring(this.item.self); - // Send a notification that the removal was successful this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); } } diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index fca5074a88..735674afc3 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -25,6 +25,7 @@ import { compareArraysUsingIds, filterRelationsByTypeLabel, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils'; +import { ObjectCacheService } from '../cache/object-cache.service'; /** * The service handling all relationship requests @@ -36,7 +37,8 @@ export class RelationshipService { constructor(protected requestService: RequestService, protected halService: HALEndpointService, protected rdbService: RemoteDataBuildService, - protected itemService: ItemDataService) { + protected itemService: ItemDataService, + protected objectCache: ObjectCacheService) { } /** @@ -49,6 +51,11 @@ export class RelationshipService { ); } + findById(uuid: string): Observable> { + const href$ = this.getRelationshipEndpoint(uuid); + return this.rdbService.buildSingle(href$); + } + /** * Send a delete request for a relationship by ID * @param uuid @@ -60,7 +67,8 @@ export class RelationshipService { map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)), - getResponseFromEntry() + getResponseFromEntry(), + tap(() => this.clearRelatedCache(uuid)) ); } @@ -203,4 +211,17 @@ export class RelationshipService { ); } + clearRelatedCache(uuid: string) { + this.findById(uuid).pipe( + getSucceededRemoteData(), + flatMap((rd: RemoteData) => observableCombineLatest(rd.payload.leftItem.pipe(getSucceededRemoteData()), rd.payload.rightItem.pipe(getSucceededRemoteData()))), + take(1) + ).subscribe(([leftItem, rightItem]) => { + this.objectCache.remove(leftItem.payload.self); + this.objectCache.remove(rightItem.payload.self); + this.requestService.removeByHrefSubstring(leftItem.payload.self); + this.requestService.removeByHrefSubstring(rightItem.payload.self); + }); + } + } From 8265942f18349bd8c6cb4d358f03a7209c9c968f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Jul 2019 13:41:20 +0200 Subject: [PATCH 27/60] 63469: Properly reload de-cached items --- .../+search-page/search-service/search.service.ts | 13 +++++++------ src/app/core/shared/operators.ts | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 598657a1b2..79164b50c3 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,7 +1,7 @@ -import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; import { NavigationExtras, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; -import { first, map, switchMap } from 'rxjs/operators'; +import { first, map, switchMap, tap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { FacetConfigSuccessResponse, @@ -23,7 +23,7 @@ import { getSucceededRemoteData } from '../../core/shared/operators'; import { URLCombiner } from '../../core/url-combiner/url-combiner'; -import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; +import { hasValue, hasValueOperator, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { NormalizedSearchResult } from '../normalized-search-result.model'; import { SearchOptions } from '../search-options.model'; import { SearchResult } from '../search-result.model'; @@ -137,10 +137,11 @@ export class SearchService implements OnDestroy { map((sqr: SearchQueryResponse) => { return sqr.objects .filter((nsr: NormalizedSearchResult) => isNotUndefined(nsr.indexableObject)) - .map((nsr: NormalizedSearchResult) => { - return this.rdb.buildSingle(nsr.indexableObject); - }) + .map((nsr: NormalizedSearchResult) => new GetRequest(this.requestService.generateRequestId(), nsr.indexableObject)) }), + // Send a request for each item to ensure fresh cache + tap((reqs: RestRequest[]) => reqs.forEach((req: RestRequest) => this.requestService.configure(req))), + map((reqs: RestRequest[]) => reqs.map((req: RestRequest) => this.rdb.buildSingle(req.href))), switchMap((input: Array>>) => this.rdb.aggregate(input)), ); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index ae46691e39..d46c688e68 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -91,7 +91,7 @@ export const toDSpaceObjectListRD = () => source.pipe( filter((rd: RemoteData>>) => rd.hasSucceeded), map((rd: RemoteData>>) => { - const dsoPage: T[] = rd.payload.page.map((searchResult: SearchResult) => searchResult.indexableObject); + const dsoPage: T[] = rd.payload.page.filter((result) => hasValue(result)).map((searchResult: SearchResult) => searchResult.indexableObject); const payload = Object.assign(rd.payload, { page: dsoPage }) as PaginatedList; return Object.assign(rd, { payload: payload }); }) From 34e75a46e5919f4aaa436e5c5c49b9b6a8cfc889 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Jul 2019 14:30:19 +0200 Subject: [PATCH 28/60] 63469: JSDocs + tests --- .../item-relationships.component.spec.ts | 4 +- .../core/data/relationship.service.spec.ts | 61 +++++++++++++------ src/app/core/data/relationship.service.ts | 8 +++ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts index 1d3d59201b..51394ef9e5 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts @@ -227,10 +227,8 @@ describe('ItemRelationshipsComponent', () => { comp.submit(); }); - it('it should delete the correct relationship and de-cache the current item', () => { + it('it should delete the correct relationship', () => { expect(relationshipService.deleteRelationship).toHaveBeenCalledWith(relationships[1].uuid); - expect(objectCache.remove).toHaveBeenCalledWith(item.self); - expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self); }); }); }); diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts index ce2b169eef..88da4a5496 100644 --- a/src/app/core/data/relationship.service.spec.ts +++ b/src/app/core/data/relationship.service.spec.ts @@ -13,6 +13,8 @@ import { Item } from '../shared/item.model'; import { PaginatedList } from './paginated-list'; import { PageInfo } from '../shared/page-info.model'; import { DeleteRequest } from './request.models'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { Observable } from 'rxjs/internal/Observable'; describe('RelationshipService', () => { let service: RelationshipService; @@ -22,6 +24,11 @@ describe('RelationshipService', () => { const relationshipsEndpointURL = `${restEndpointURL}/relationships`; const halService: any = new HALEndpointServiceStub(restEndpointURL); const rdbService = getMockRemoteDataBuildService(); + const objectCache = Object.assign({ + /* tslint:disable:no-empty */ + remove: () => {} + /* tslint:enable:no-empty */ + }) as ObjectCacheService; const relationshipType = Object.assign(new RelationshipType(), { type: ResourceType.RelationshipType, @@ -31,24 +38,20 @@ describe('RelationshipService', () => { rightLabel: 'isPublicationOfAuthor' }); - const relationships = [ - Object.assign(new Relationship(), { - self: relationshipsEndpointURL + '/2', - id: '2', - uuid: '2', - leftId: 'author1', - rightId: 'publication', - relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) - }), - Object.assign(new Relationship(), { - self: relationshipsEndpointURL + '/3', - id: '3', - uuid: '3', - leftId: 'author2', - rightId: 'publication', - relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) - }) - ]; + const relationship1 = Object.assign(new Relationship(), { + self: relationshipsEndpointURL + '/2', + id: '2', + uuid: '2', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }); + const relationship2 = Object.assign(new Relationship(), { + self: relationshipsEndpointURL + '/3', + id: '3', + uuid: '3', + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }); + + const relationships = [ relationship1, relationship2 ]; const item = Object.assign(new Item(), { self: 'fake-item-url/publication', @@ -65,6 +68,10 @@ describe('RelationshipService', () => { id: 'author2', uuid: 'author2' }); + relationship1.leftItem = getRemotedataObservable(relatedItem1); + relationship1.rightItem = getRemotedataObservable(item); + relationship2.leftItem = getRemotedataObservable(relatedItem2); + relationship2.rightItem = getRemotedataObservable(item); const relatedItems = [relatedItem1, relatedItem2]; const itemService = jasmine.createSpyObj('itemService', { @@ -76,7 +83,8 @@ describe('RelationshipService', () => { requestService, halService, rdbService, - itemService + itemService, + objectCache ); } @@ -93,13 +101,22 @@ describe('RelationshipService', () => { describe('deleteRelationship', () => { beforeEach(() => { + spyOn(service, 'findById').and.returnValue(getRemotedataObservable(relationship1)); + spyOn(objectCache, 'remove'); service.deleteRelationship(relationships[0].uuid).subscribe(); }); it('should send a DeleteRequest', () => { - const expected = new DeleteRequest(requestService.generateRequestId(), relationshipsEndpointURL + '/' + relationships[0].uuid); + const expected = new DeleteRequest(requestService.generateRequestId(), relationshipsEndpointURL + '/' + relationship1.uuid); expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); }); + + it('should clear the related items their cache', () => { + expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self); + expect(objectCache.remove).toHaveBeenCalledWith(item.self); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.self); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self); + }); }); describe('getItemRelationshipsArray', () => { @@ -135,3 +152,7 @@ describe('RelationshipService', () => { }) }); + +function getRemotedataObservable(obj: any): Observable> { + return observableOf(new RemoteData(false, false, true, undefined, obj)); +} diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 735674afc3..1699b6a27d 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -51,6 +51,10 @@ export class RelationshipService { ); } + /** + * Find a relationship by its UUID + * @param uuid + */ findById(uuid: string): Observable> { const href$ = this.getRelationshipEndpoint(uuid); return this.rdbService.buildSingle(href$); @@ -211,6 +215,10 @@ export class RelationshipService { ); } + /** + * Clear object and request caches of the items related to a relationship (left and right items) + * @param uuid + */ clearRelatedCache(uuid: string) { this.findById(uuid).pipe( getSucceededRemoteData(), From bd8177c17d88e55f7f120855e67f3cfbd7f6f4a7 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Jun 2019 17:46:45 +0200 Subject: [PATCH 29/60] 62741: AoT build fix --- .../object-list/item-type-badge/item-type-badge.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts index 53e36a535d..9ffba33758 100644 --- a/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts +++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts @@ -1,10 +1,12 @@ import { Component, Input } from '@angular/core'; import { ListableObject } from '../../object-collection/shared/listable-object.model'; +import { SearchResult } from '../../../+search-page/search-result.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @Component({ selector: 'ds-item-type-badge', templateUrl: './item-type-badge.component.html' }) export class ItemTypeBadgeComponent { - @Input() object: ListableObject; + @Input() object: SearchResult; } From c2345c1562e1af83a506330c4c48ca7717f1cd27 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 18 Jul 2019 11:51:46 +0200 Subject: [PATCH 30/60] 63825: UI language cookie --- src/app/app.component.spec.ts | 3 ++ src/app/app.component.ts | 22 ++++++++-- src/app/app.module.ts | 4 +- .../lang-switch/lang-switch.component.html | 2 +- .../lang-switch/lang-switch.component.spec.ts | 40 ++++++++++++++++++- .../lang-switch/lang-switch.component.ts | 14 ++++++- .../mocks/mock-client-cookie.service.ts | 26 ++++++++++++ 7 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/app/shared/mocks/mock-client-cookie.service.ts diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index bd2d832c67..016d8df8d4 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -44,6 +44,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouteService } from './shared/services/route.service'; import { MockActivatedRoute } from './shared/mocks/mock-active-router'; import { MockRouter } from './shared/mocks/mock-router'; +import { ClientCookieService } from './shared/services/client-cookie.service'; +import { MockClientCookieService } from './shared/mocks/mock-client-cookie.service'; let comp: AppComponent; let fixture: ComponentFixture; @@ -78,6 +80,7 @@ describe('App component', () => { { provide: MenuService, useValue: menuService }, { provide: CSSVariableService, useClass: CSSVariableServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(800) }, + { provide: ClientCookieService, useValue: new MockClientCookieService()}, AppComponent, RouteService ], diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 52c169e7bc..cc71247d9f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,6 +32,10 @@ import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs'; import { slideSidebarPadding } from './shared/animations/slide'; import { HostWindowService } from './shared/host-window.service'; import { Theme } from '../config/theme.inferface'; +import { ClientCookieService } from './shared/services/client-cookie.service'; +import { isNotEmpty } from './shared/empty.util'; + +export const LANG_COOKIE = 'language_cookie'; @Component({ selector: 'ds-app', @@ -61,6 +65,7 @@ export class AppComponent implements OnInit, AfterViewInit { private cssService: CSSVariableService, private menuService: MenuService, private windowService: HostWindowService, + private clientCookie: ClientCookieService ) { // Load all the languages that are defined as active from the config file translate.addLangs(config.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code)); @@ -68,11 +73,20 @@ export class AppComponent implements OnInit, AfterViewInit { // Load the default language from the config file translate.setDefaultLang(config.defaultLanguage); - // Attempt to get the browser language from the user - if (translate.getLangs().includes(translate.getBrowserLang())) { - translate.use(translate.getBrowserLang()); + // Attempt to get the language from a cookie + const lang = clientCookie.get(LANG_COOKIE); + if (isNotEmpty(lang)) { + // Cookie found + // Use the language from the cookie + translate.use(lang); } else { - translate.use(config.defaultLanguage); + // Cookie not found + // Attempt to get the browser language from the user + if (translate.getLangs().includes(translate.getBrowserLang())) { + translate.use(translate.getBrowserLang()); + } else { + translate.use(config.defaultLanguage); + } } metadata.listenForRouteChange(); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ce5a2d78a2..3781edf532 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,6 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e import { NavbarModule } from './navbar/navbar.module'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; +import { ClientCookieService } from './shared/services/client-cookie.service'; export function getConfig() { return ENV_CONFIG; @@ -97,7 +98,8 @@ const PROVIDERS = [ { provide: RouterStateSerializer, useClass: DSpaceRouterStateSerializer - } + }, + ClientCookieService ]; const DECLARATIONS = [ diff --git a/src/app/shared/lang-switch/lang-switch.component.html b/src/app/shared/lang-switch/lang-switch.component.html index 745facc95c..b61ec5592e 100644 --- a/src/app/shared/lang-switch/lang-switch.component.html +++ b/src/app/shared/lang-switch/lang-switch.component.html @@ -4,7 +4,7 @@
+
+ + +
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index b6e3b7e989..be82e26033 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -1,6 +1,5 @@ import { BitstreamFormatsComponent } from './bitstream-formats.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RegistryService } from '../../../core/registry/registry.service'; import { of as observableOf } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; @@ -13,85 +12,288 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub'; +import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; +import { ResourceType } from '../../../core/shared/resource-type'; +import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level'; +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { TestScheduler } from 'rxjs/testing'; describe('BitstreamFormatsComponent', () => { let comp: BitstreamFormatsComponent; let fixture: ComponentFixture; - let registryService: RegistryService; - const mockFormatsList = [ - { - shortDescription: 'Unknown', - description: 'Unknown data format', - mimetype: 'application/octet-stream', - supportLevel: 0, - internal: false, - extensions: null - }, - { - shortDescription: 'License', - description: 'Item-specific license agreed upon to submission', - mimetype: 'text/plain; charset=utf-8', - supportLevel: 1, - internal: true, - extensions: null - }, - { - shortDescription: 'CC License', - description: 'Item-specific Creative Commons license agreed upon to submission', - mimetype: 'text/html; charset=utf-8', - supportLevel: 2, - internal: true, - extensions: null - }, - { - shortDescription: 'Adobe PDF', - description: 'Adobe Portable Document Format', - mimetype: 'application/pdf', - supportLevel: 0, - internal: false, - extensions: null - } - ]; - const mockFormats = observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList))); - const registryServiceStub = { - getBitstreamFormats: () => mockFormats + let bitstreamFormatService; + let scheduler: TestScheduler; + let notificationsServiceStub; + + const bitstreamFormat1: BitstreamFormat = { + uuid: 'test-uuid-1', + id: 'test-uuid-1', + shortDescription: 'Unknown', + description: 'Unknown data format', + mimetype: 'application/octet-stream', + supportLevel: BitstreamFormatSupportLevel.Unknown, + internal: false, + extensions: null, + type: ResourceType.BitstreamFormat, + self: 'self-link' + }; + const bitstreamFormat2: BitstreamFormat = { + uuid: 'test-uuid-2', + id: 'test-uuid-2', + shortDescription: 'License', + description: 'Item-specific license agreed upon to submission', + mimetype: 'text/plain; charset=utf-8', + supportLevel: BitstreamFormatSupportLevel.Known, + internal: true, + extensions: null, + type: ResourceType.BitstreamFormat, + self: 'self-link' + }; + const bitstreamFormat3: BitstreamFormat = { + uuid: 'test-uuid-3', + id: 'test-uuid-3', + shortDescription: 'CC License', + description: 'Item-specific Creative Commons license agreed upon to submission', + mimetype: 'text/html; charset=utf-8', + supportLevel: BitstreamFormatSupportLevel.Supported, + internal: true, + extensions: null, + type: ResourceType.BitstreamFormat, + self: 'self-link' + }; + const bitstreamFormat4: BitstreamFormat = { + uuid: 'test-uuid-4', + id: 'test-uuid-4', + shortDescription: 'Adobe PDF', + description: 'Adobe Portable Document Format', + mimetype: 'application/pdf', + supportLevel: BitstreamFormatSupportLevel.Unknown, + internal: false, + extensions: null, + type: ResourceType.BitstreamFormat, + self: 'self-link' }; - beforeEach(async(() => { + const mockFormatsList: BitstreamFormat[] = [ + bitstreamFormat1, + bitstreamFormat2, + bitstreamFormat3, + bitstreamFormat4 + ]; + const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)); + + const initAsync = () => { + notificationsServiceStub = new NotificationsServiceStub(); + + scheduler = getTestScheduler(); + + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: observableOf(mockFormatsRD), + find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), + getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}), + selectBitstreamFormat: {}, + deselectBitstreamFormat: {}, + deselectAllBitstreamFormats: {}, + delete: observableOf(true), + clearBitStreamFormatRequests: observableOf('cleared') + }); + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], providers: [ - { provide: RegistryService, useValue: registryServiceStub }, - { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + {provide: HostWindowService, useValue: new HostWindowServiceStub(0)}, + {provide: NotificationsService, useValue: notificationsServiceStub} ] }).compileComponents(); - })); + }; - beforeEach(() => { + const initBeforeEach = () => { fixture = TestBed.createComponent(BitstreamFormatsComponent); comp = fixture.componentInstance; fixture.detectChanges(); - registryService = (comp as any).service; + }; + + describe('Bitstream format page content', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + + it('should contain four formats', () => { + const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement; + expect(tbody.children.length).toBe(4); + }); + + it('should contain the correct formats', () => { + const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement; + expect(unknownName.textContent).toBe('Unknown'); + + const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement; + expect(licenseName.textContent).toBe('License'); + + const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement; + expect(ccLicenseName.textContent).toBe('CC License'); + + const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement; + expect(adobeName.textContent).toBe('Adobe PDF'); + }); }); - it('should contain four formats', () => { - const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement; - expect(tbody.children.length).toBe(4); + describe('selectBitStreamFormat', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should select a bitstreamFormat if it was selected in the event', () => { + const event = {target: {checked: true}}; + + comp.selectBitStreamFormat(bitstreamFormat1, event); + + expect(bitstreamFormatService.selectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1); + }); + it('should deselect a bitstreamFormat if it is deselected in the event', () => { + const event = {target: {checked: false}}; + + comp.selectBitStreamFormat(bitstreamFormat1, event); + + expect(bitstreamFormatService.deselectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1); + }); + it('should be called when a user clicks a checkbox', () => { + spyOn(comp, 'selectBitStreamFormat'); + const unknownFormat = fixture.debugElement.query(By.css('#formats tr:nth-child(1) input')); + + const event = {target: {checked: true}}; + unknownFormat.triggerEventHandler('change', event); + + expect(comp.selectBitStreamFormat).toHaveBeenCalledWith(bitstreamFormat1, event); + }); }); - it('should contain the correct formats', () => { - const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement; - expect(unknownName.textContent).toBe('Unknown'); + describe('isSelected', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should return an observable of true if the provided bistream is in the list returned by the service', () => { + const result = comp.isSelected(bitstreamFormat1); - const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement; - expect(licenseName.textContent).toBe('License'); + expect(result).toBeObservable(cold('b', {b: true})); + }); + it('should return an observable of false if the provided bistream is not in the list returned by the service', () => { + const format = new BitstreamFormat(); + format.uuid = 'new'; - const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement; - expect(ccLicenseName.textContent).toBe('CC License'); + const result = comp.isSelected(format); - const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement; - expect(adobeName.textContent).toBe('Adobe PDF'); + expect(result).toBeObservable(cold('b', {b: false})); + }); }); + describe('deselectAll', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should deselect all bitstreamFormats', () => { + comp.deselectAll(); + expect(bitstreamFormatService.deselectAllBitstreamFormats).toHaveBeenCalled(); + }); + + it('should be called when the deselect all button is clicked', () => { + spyOn(comp, 'deselectAll'); + const deselectAllButton = fixture.debugElement.query(By.css('button.deselect')); + deselectAllButton.triggerEventHandler('click', null); + + expect(comp.deselectAll).toHaveBeenCalled(); + + }); + }); + + describe('deleteFormats success', () => { + beforeEach(async(() => { + notificationsServiceStub = new NotificationsServiceStub(); + + scheduler = getTestScheduler(); + + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: observableOf(mockFormatsRD), + find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), + getSelectedBitstreamFormats: observableOf(mockFormatsList), + selectBitstreamFormat: {}, + deselectBitstreamFormat: {}, + deselectAllBitstreamFormats: {}, + delete: observableOf(true), + clearBitStreamFormatRequests: observableOf('cleared') + }); + + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], + providers: [ + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + {provide: HostWindowService, useValue: new HostWindowServiceStub(0)}, + {provide: NotificationsService, useValue: notificationsServiceStub} + ] + }).compileComponents(); + } + )); + + beforeEach(initBeforeEach); + it('should clear bitstream formats ', () => { + comp.deleteFormats(); + + expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled(); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4); + + expect(notificationsServiceStub.success).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.success.head', + 'admin.registries.bitstream-formats.delete.success.amount'); + expect(notificationsServiceStub.error).not.toHaveBeenCalled(); + + }); + }); + + describe('deleteFormats error', () => { + beforeEach(async(() => { + notificationsServiceStub = new NotificationsServiceStub(); + + scheduler = getTestScheduler(); + + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: observableOf(mockFormatsRD), + find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), + getSelectedBitstreamFormats: observableOf(mockFormatsList), + selectBitstreamFormat: {}, + deselectBitstreamFormat: {}, + deselectAllBitstreamFormats: {}, + delete: observableOf(false), + clearBitStreamFormatRequests: observableOf('cleared') + }); + + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe], + providers: [ + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + {provide: HostWindowService, useValue: new HostWindowServiceStub(0)}, + {provide: NotificationsService, useValue: notificationsServiceStub} + ] + }).compileComponents(); + } + )); + + beforeEach(initBeforeEach); + it('should clear bitstream formats ', () => { + comp.deleteFormats(); + + expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled(); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3); + expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4); + + expect(notificationsServiceStub.error).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.failure.head', + 'admin.registries.bitstream-formats.delete.failure.amount'); + expect(notificationsServiceStub.success).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index bc0cbb8da6..5a619e3e05 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,10 +1,16 @@ -import { Component } from '@angular/core'; -import { RegistryService } from '../../../core/registry/registry.service'; -import { Observable } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; -import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; +import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; +import { FindAllOptions } from '../../../core/data/request.models'; +import { map, switchMap, take } from 'rxjs/operators'; +import { hasValue } from '../../../shared/empty.util'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; /** * This component renders a list of bitstream formats @@ -13,24 +19,122 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio selector: 'ds-bitstream-formats', templateUrl: './bitstream-formats.component.html' }) -export class BitstreamFormatsComponent { +export class BitstreamFormatsComponent implements OnInit { /** * A paginated list of bitstream formats to be shown on the page */ bitstreamFormats: Observable>>; + /** + * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats + */ + pageState: BehaviorSubject; + + /** + * The current pagination configuration for the page used by the FindAll method + * Currently simply renders all bitstream formats + */ + config: FindAllOptions = Object.assign(new FindAllOptions(), { + elementsPerPage: 10000 + }); + /** * The current pagination configuration for the page * Currently simply renders all bitstream formats */ - config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { id: 'registry-bitstreamformats-pagination', pageSize: 10000 }); - constructor(private registryService: RegistryService) { - this.updateFormats(); + constructor(private notificationsService: NotificationsService, + private router: Router, + private translateService: TranslateService, + private bitstreamFormatService: BitstreamFormatDataService) { + } + + /** + * Deletes the currently selected formats from the registry and updates the presented list + */ + deleteFormats() { + this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe(); + this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe( + (formats) => { + const tasks$ = []; + for (const format of formats) { + if (hasValue(format.id)) { + tasks$.push(this.bitstreamFormatService.delete(format)); + } + } + zip(...tasks$).subscribe((results: boolean[]) => { + const successResponses = results.filter((result: boolean) => result); + const failedResponses = results.filter((result: boolean) => !result); + if (successResponses.length > 0) { + this.showNotification(true, successResponses.length); + } + if (failedResponses.length > 0) { + this.showNotification(false, failedResponses.length); + } + + this.deselectAll(); + this.pageState.next('update-on-delete'); + }); + } + ); + } + + /** + * Deselects all selecetd bitstream formats + */ + deselectAll() { + this.bitstreamFormatService.deselectAllBitstreamFormats(); + } + + /** + * Checks whether a given bitstream format is selected in the list (checkbox) + * @param bitstreamFormat + */ + isSelected(bitstreamFormat: BitstreamFormat): Observable { + return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe( + map((bitstreamFormats: BitstreamFormat[]) => { + return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null; + }) + ); + } + + /** + * Selects or deselects a bitstream format based on the checkbox state + * @param bitstreamFormat + * @param event + */ + selectBitStreamFormat(bitstreamFormat: BitstreamFormat, event) { + event.target.checked ? + this.bitstreamFormatService.selectBitstreamFormat(bitstreamFormat) : + this.bitstreamFormatService.deselectBitstreamFormat(bitstreamFormat); + } + + /** + * Show notifications for an amount of deleted bitstream formats + * @param success Whether or not the notification should be a success message (error message when false) + * @param amount The amount of deleted bitstream formats + */ + private showNotification(success: boolean, amount: number) { + const prefix = 'admin.registries.bitstream-formats.delete'; + const suffix = success ? 'success' : 'failure'; + + const messages = observableCombineLatest( + this.translateService.get(`${prefix}.${suffix}.head`), + this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount}) + ); + messages.subscribe(([head, content]) => { + + if (success) { + this.notificationsService.success(head, content); + } else { + this.notificationsService.error(head, content); + } + }); } /** @@ -39,13 +143,23 @@ export class BitstreamFormatsComponent { */ onPageChange(event) { this.config.currentPage = event; + this.pageConfig.currentPage = event; this.updateFormats(); } + ngOnInit(): void { + this.pageState = new BehaviorSubject('init'); + this.bitstreamFormats = this.pageState.pipe( + switchMap(() => { + return this.updateFormats() + ; + })); + } + /** - * Method to update the bitstream formats that are shown + * Finds all formats based on the current config */ private updateFormats() { - this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config); + return this.bitstreamFormatService.findAll(this.config); } } diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts new file mode 100644 index 0000000000..0800c50169 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { BitstreamFormatsComponent } from './bitstream-formats.component'; +import { SharedModule } from '../../../shared/shared.module'; +import { FormatFormComponent } from './format-form/format-form.component'; +import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component'; +import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module'; +import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + RouterModule, + TranslateModule, + BitstreamFormatsRoutingModule + ], + declarations: [ + BitstreamFormatsComponent, + EditBitstreamFormatComponent, + AddBitstreamFormatComponent, + FormatFormComponent + ], + entryComponents: [] +}) +export class BitstreamFormatsModule { + +} diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts new file mode 100644 index 0000000000..f6eef741fd --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { find } from 'rxjs/operators'; +import { RemoteData } from '../../../core/data/remote-data'; +import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; +import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; +import { hasValue } from '../../../shared/empty.util'; + +/** + * This class represents a resolver that requests a specific bitstreamFormat before the route is activated + */ +@Injectable() +export class BitstreamFormatsResolver implements Resolve> { + constructor(private bitstreamFormatDataService: BitstreamFormatDataService) { + } + + /** + * Method for resolving an bitstreamFormat based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable<> Emits the found bitstreamFormat based on the parameters in the current route, + * or an error if something went wrong + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + return this.bitstreamFormatDataService.findById(route.params.id) + .pipe( + find((RD) => hasValue(RD.error) || RD.hasSucceeded), + ); + } +} diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html new file mode 100644 index 0000000000..8e7f2c7451 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html @@ -0,0 +1,11 @@ +
+
+
+

{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}

+ + + +
+
+
\ No newline at end of file diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts new file mode 100644 index 0000000000..0f2c0808a2 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts @@ -0,0 +1,126 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterStub } from '../../../../shared/testing/router-stub'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { EditBitstreamFormatComponent } from './edit-bitstream-format.component'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub'; +import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; +import { RestResponse } from '../../../../core/cache/response.models'; +import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; +import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; +import { ResourceType } from '../../../../core/shared/resource-type'; + +describe('EditBitstreamFormatComponent', () => { + let comp: EditBitstreamFormatComponent; + let fixture: ComponentFixture; + + const bitstreamFormat: BitstreamFormat = { + uuid: 'test-uuid', + id: 'test-uuid', + shortDescription: 'Adobe PDF', + description: 'Adobe Portable Document Format', + mimetype: 'application/pdf', + supportLevel: BitstreamFormatSupportLevel.Unknown, + internal: false, + extensions: ['pdf', 'also-pdf'], + type: ResourceType.BitstreamFormat, + self: 'self-link' + }; + + const routeStub = { + data: observableOf({ + bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat) + }) + }; + + let router; + let notificationService: NotificationsServiceStub; + let bitstreamFormatDataService: BitstreamFormatDataService; + + const initAsync = () => { + router = new RouterStub(); + notificationService = new NotificationsServiceStub(); + bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { + updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')) + }); + + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [EditBitstreamFormatComponent], + providers: [ + {provide: ActivatedRoute, useValue: routeStub}, + {provide: Router, useValue: router}, + {provide: NotificationsService, useValue: notificationService}, + {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService}, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); + }; + + const initBeforeEach = () => { + fixture = TestBed.createComponent(EditBitstreamFormatComponent); + comp = fixture.componentInstance; + + fixture.detectChanges(); + }; + + describe('init', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should initialise the bitstreamFormat based on the route', () => { + + comp.bitstreamFormatRD$.subscribe((format: RemoteData) => { + expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat)); + }); + }); + }); + describe('updateFormat success', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should send the updated form to the service, show a notification and navigate to ', () => { + comp.updateFormat(bitstreamFormat); + + expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat); + expect(notificationService.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']); + + }); + }); + describe('updateFormat error', () => { + beforeEach(async( () => { + router = new RouterStub(); + notificationService = new NotificationsServiceStub(); + bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { + updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')) + }); + + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [EditBitstreamFormatComponent], + providers: [ + {provide: ActivatedRoute, useValue: routeStub}, + {provide: Router, useValue: router}, + {provide: NotificationsService, useValue: notificationService}, + {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService}, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); + })); + beforeEach(initBeforeEach); + it('should send the updated form to the service, show a notification and navigate to ', () => { + comp.updateFormat(bitstreamFormat); + + expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat); + expect(notificationService.error).toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); + + }); + }); +}); diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts new file mode 100644 index 0000000000..0fdcc75689 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts @@ -0,0 +1,62 @@ +import { map, take } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; +import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; +import { RestResponse } from '../../../../core/cache/response.models'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * This component renders the edit page of a bitstream format. + * The route parameter 'id' is used to request the bitstream format. + */ +@Component({ + selector: 'ds-edit-bitstream-format', + templateUrl: './edit-bitstream-format.component.html', +}) +export class EditBitstreamFormatComponent implements OnInit { + + /** + * The bitstream format wrapped in a remote-data object + */ + bitstreamFormatRD$: Observable>; + + constructor( + private route: ActivatedRoute, + private router: Router, + private notificationService: NotificationsService, + private translateService: TranslateService, + private bitstreamFormatDataService: BitstreamFormatDataService, + ) { + } + + ngOnInit(): void { + this.bitstreamFormatRD$ = this.route.data.pipe( + map((data) => data.bitstreamFormat as RemoteData) + ); + } + + /** + * Updates the bitstream format based on the provided bitstream format emitted by the form. + * When successful, a success notification will be shown and the user will be navigated back to the overview page. + * When failed, an error notification will be shown. + */ + updateFormat(bitstreamFormat: BitstreamFormat) { + this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1) + ).subscribe((response: RestResponse) => { + if (response.isSuccessful) { + this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'), + this.translateService.get('admin.registries.bitstream-formats.edit.success.content')); + this.router.navigate([getBitstreamFormatsModulePath()]); + } else { + this.notificationService.error('admin.registries.bitstream-formats.edit.failure.head', + 'admin.registries.bitstream-formats.create.edit.content'); + } + } + ); + } +} diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html new file mode 100644 index 0000000000..be6ebf2599 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts new file mode 100644 index 0000000000..9a91b003a0 --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts @@ -0,0 +1,108 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { RouterStub } from '../../../../shared/testing/router-stub'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { FormatFormComponent } from './format-form.component'; +import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; +import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; +import { ResourceType } from '../../../../core/shared/resource-type'; +import { DynamicCheckboxModel, DynamicFormArrayModel, DynamicInputModel } from '@ng-dynamic-forms/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { isEmpty } from '../../../../shared/empty.util'; + +describe('FormatFormComponent', () => { + let comp: FormatFormComponent; + let fixture: ComponentFixture; + + const router = new RouterStub(); + + const bitstreamFormat: BitstreamFormat = { + uuid: 'test-uuid', + id: 'test-uuid', + shortDescription: 'Adobe PDF', + description: 'Adobe Portable Document Format', + mimetype: 'application/pdf', + supportLevel: BitstreamFormatSupportLevel.Unknown, + internal: false, + extensions: ['pdf', 'also-pdf'], + type: ResourceType.BitstreamFormat, + self: 'self-link' + }; + + const submittedBitstreamFormat = new BitstreamFormat(); + submittedBitstreamFormat.id = bitstreamFormat.id; + submittedBitstreamFormat.shortDescription = bitstreamFormat.shortDescription; + submittedBitstreamFormat.mimetype = bitstreamFormat.mimetype; + submittedBitstreamFormat.description = bitstreamFormat.description; + submittedBitstreamFormat.supportLevel = bitstreamFormat.supportLevel; + submittedBitstreamFormat.internal = bitstreamFormat.internal; + submittedBitstreamFormat.extensions = bitstreamFormat.extensions; + + const initAsync = () => { + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [FormatFormComponent], + providers: [ + {provide: Router, useValue: router}, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }).compileComponents(); + }; + + const initBeforeEach = () => { + fixture = TestBed.createComponent(FormatFormComponent); + comp = fixture.componentInstance; + + comp.bitstreamFormat = bitstreamFormat; + fixture.detectChanges(); + }; + + describe('initialise', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + it('should initialises the values in the form', () => { + + expect((comp.formModel[0] as DynamicInputModel).value).toBe(bitstreamFormat.shortDescription); + expect((comp.formModel[1] as DynamicInputModel).value).toBe(bitstreamFormat.mimetype); + expect((comp.formModel[2] as DynamicInputModel).value).toBe(bitstreamFormat.description); + expect((comp.formModel[3] as DynamicInputModel).value).toBe(bitstreamFormat.supportLevel); + expect((comp.formModel[4] as DynamicCheckboxModel).value).toBe(bitstreamFormat.internal); + + const formArray = (comp.formModel[5] as DynamicFormArrayModel); + const extensions = []; + for (let i = 0; i < formArray.groups.length; i++) { + const value = (formArray.get(i).get(0) as DynamicInputModel).value; + if (!isEmpty(value)) { + extensions.push((formArray.get(i).get(0) as DynamicInputModel).value); + } + } + + expect(extensions).toEqual(bitstreamFormat.extensions); + + }); + }); + describe('onSubmit', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + + it('should emit the bitstreamFormat currently present in the form', () => { + spyOn(comp.updatedFormat, 'emit'); + comp.onSubmit(); + + expect(comp.updatedFormat.emit).toHaveBeenCalledWith(submittedBitstreamFormat); + }); + }); + describe('onCancel', () => { + beforeEach(async(initAsync)); + beforeEach(initBeforeEach); + + it('should navigate back to the bitstream overview', () => { + comp.onCancel(); + expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']); + }); + }); +}); diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts new file mode 100644 index 0000000000..febf56bc9f --- /dev/null +++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts @@ -0,0 +1,193 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; +import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; +import { + DynamicCheckboxModel, + DynamicFormArrayModel, + DynamicFormControlLayout, + DynamicFormControlModel, + DynamicFormService, + DynamicInputModel, + DynamicSelectModel, + DynamicTextAreaModel +} from '@ng-dynamic-forms/core'; +import { Router } from '@angular/router'; +import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module'; +import { hasValue, isEmpty } from '../../../../shared/empty.util'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * The component responsible for rendering the form to create/edit a bitstream format + */ +@Component({ + selector: 'ds-bitstream-format-form', + templateUrl: './format-form.component.html' +}) +export class FormatFormComponent implements OnInit { + + /** + * The current bitstream format + * This can either be and existing one or a new one + */ + @Input() bitstreamFormat: BitstreamFormat = new BitstreamFormat(); + + /** + * EventEmitter that will emit the updated bitstream format + */ + @Output() updatedFormat: EventEmitter = new EventEmitter(); + + /** + * The different supported support level of the bitstream format + */ + supportLevelOptions = [{label: BitstreamFormatSupportLevel.Known, value: BitstreamFormatSupportLevel.Known}, + {label: BitstreamFormatSupportLevel.Unknown, value: BitstreamFormatSupportLevel.Unknown}, + {label: BitstreamFormatSupportLevel.Supported, value: BitstreamFormatSupportLevel.Supported}]; + + /** + * Styling element for repeatable field + */ + arrayElementLayout: DynamicFormControlLayout = { + grid: { + group: 'form-row', + } + }; + + /** + * Styling element for element of repeatable field + */ + arrayInputElementLayout: DynamicFormControlLayout = { + grid: { + host: 'col' + } + }; + + /** + * The form model representing the bitstream format + */ + formModel: DynamicFormControlModel[] = [ + new DynamicInputModel({ + id: 'shortDescription', + name: 'shortDescription', + label: 'admin.registries.bitstream-formats.edit.shortDescription.label', + hint: 'admin.registries.bitstream-formats.edit.shortDescription.hint', + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'Please enter a name for this bitstream format' + }, + }), + new DynamicInputModel({ + id: 'mimetype', + name: 'mimetype', + label: 'admin.registries.bitstream-formats.edit.mimetype.label', + hint: 'admin.registries.bitstream-formats.edit.mimetype.hint', + + }), + new DynamicTextAreaModel({ + id: 'description', + name: 'description', + label: 'admin.registries.bitstream-formats.edit.description.label', + hint: 'admin.registries.bitstream-formats.edit.description.hint', + + }), + new DynamicSelectModel({ + id: 'supportLevel', + name: 'supportLevel', + options: this.supportLevelOptions, + label: 'admin.registries.bitstream-formats.edit.supportLevel.label', + hint: 'admin.registries.bitstream-formats.edit.supportLevel.hint', + value: this.supportLevelOptions[0].value + + }), + new DynamicCheckboxModel({ + id: 'internal', + name: 'internal', + label: 'Internal', + hint: 'admin.registries.bitstream-formats.edit.internal.hint', + }), + new DynamicFormArrayModel({ + id: 'extensions', + name: 'extensions', + label: 'admin.registries.bitstream-formats.edit.extensions.label', + groupFactory: () => [ + new DynamicInputModel({ + id: 'extension', + }, this.arrayInputElementLayout) + ] + }, this.arrayElementLayout), + ]; + + constructor(private dynamicFormService: DynamicFormService, + private translateService: TranslateService, + private router: Router) { + + } + + ngOnInit(): void { + + this.initValues(); + } + + /** + * Initializes the form based on the provided bitstream format + */ + initValues() { + this.formModel.forEach( + (fieldModel: DynamicFormControlModel) => { + if (fieldModel.name === 'extensions') { + if (hasValue(this.bitstreamFormat.extensions)) { + const extenstions = this.bitstreamFormat.extensions; + const formArray = (fieldModel as DynamicFormArrayModel); + for (let i = 0; i < extenstions.length; i++) { + formArray.insertGroup(i).group[0] = new DynamicInputModel({ + id: `extension-${i}`, + value: extenstions[i] + }, this.arrayInputElementLayout); + } + } + } else { + if (hasValue(this.bitstreamFormat[fieldModel.name])) { + (fieldModel as DynamicInputModel).value = this.bitstreamFormat[fieldModel.name]; + } + } + }); + } + + /** + * Creates an updated bistream format based on the current values in the form + * Emits the updated bitstream format trouhg the updatedFormat emitter + */ + onSubmit() { + const updatedBitstreamFormat = Object.assign(new BitstreamFormat(), + { + id: this.bitstreamFormat.id + }); + + this.formModel.forEach( + (fieldModel: DynamicFormControlModel) => { + if (fieldModel.name === 'extensions') { + const formArray = (fieldModel as DynamicFormArrayModel); + const extensions = []; + for (let i = 0; i < formArray.groups.length; i++) { + const value = (formArray.get(i).get(0) as DynamicInputModel).value; + if (!isEmpty(value)) { + extensions.push((formArray.get(i).get(0) as DynamicInputModel).value); + } + } + updatedBitstreamFormat.extensions = extensions; + } else { + updatedBitstreamFormat[fieldModel.name] = (fieldModel as DynamicInputModel).value; + } + }); + this.updatedFormat.emit(updatedBitstreamFormat); + } + + /** + * Cancels the edit/create action of the bitstream format and navigates back to the bitstream format registry + */ + onCancel() { + this.router.navigate([getBitstreamFormatsModulePath()]); + } +} diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts index 71af51c683..2003ecf124 100644 --- a/src/app/+admin/admin-routing.module.ts +++ b/src/app/+admin/admin-routing.module.ts @@ -1,11 +1,19 @@ import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; +import { URLCombiner } from '../core/url-combiner/url-combiner'; +import { getAdminModulePath } from '../app-routing.module'; + +const REGISTRIES_MODULE_PATH = 'registries'; + +export function getRegistriesModulePath() { + return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString(); +} @NgModule({ imports: [ RouterModule.forChild([ { - path: 'registries', + path: REGISTRIES_MODULE_PATH, loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule' } ]) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index cb80d0165e..34bba9513b 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -16,6 +16,12 @@ const COMMUNITY_MODULE_PATH = 'communities'; export function getCommunityModulePath() { return `/${COMMUNITY_MODULE_PATH}`; } + +const ADMIN_MODULE_PATH = 'admin'; +export function getAdminModulePath() { + return `/${ADMIN_MODULE_PATH}`; +} + @NgModule({ imports: [ RouterModule.forRoot([ @@ -27,7 +33,7 @@ export function getCommunityModulePath() { { path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] }, { path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' }, { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' }, - { path: 'admin', loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] }, + { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] }, { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index ea2512a974..e3333fb34a 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -23,6 +23,10 @@ import { hasValue } from './shared/empty.util'; import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer'; import { menusReducer, MenusState } from './shared/menu/menu.reducer'; import { historyReducer, HistoryState } from './shared/history/history.reducer'; +import { + bitstreamFormatReducer, + BitstreamFormatRegistryState +} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers'; export interface AppState { router: fromRouter.RouterReducerState; @@ -30,6 +34,7 @@ export interface AppState { hostWindow: HostWindowState; forms: FormState; metadataRegistry: MetadataRegistryState; + bitstreamFormats: BitstreamFormatRegistryState; notifications: NotificationsState; searchSidebar: SearchSidebarState; searchFilter: SearchFiltersState; @@ -44,6 +49,7 @@ export const appReducers: ActionReducerMap = { hostWindow: hostWindowReducer, forms: formReducer, metadataRegistry: metadataRegistryReducer, + bitstreamFormats: bitstreamFormatReducer, notifications: notificationsReducer, searchSidebar: sidebarReducer, searchFilter: filterReducer, diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts index 994792d535..798aec8dc2 100644 --- a/src/app/core/cache/models/normalized-bitstream-format.model.ts +++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts @@ -4,7 +4,7 @@ import { BitstreamFormat } from '../../shared/bitstream-format.model'; import { mapsTo } from '../builders/build-decorators'; import { IDToUUIDSerializer } from '../id-to-uuid-serializer'; import { NormalizedObject } from './normalized-object.model'; -import { SupportLevel } from './support-level.model'; +import { BitstreamFormatSupportLevel } from '../../shared/bitstream-format-support-level'; /** * Normalized model class for a Bitstream Format @@ -35,7 +35,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject * The level of support the system offers for this Bitstream Format */ @autoserialize - supportLevel: SupportLevel; + supportLevel: BitstreamFormatSupportLevel; /** * True if the Bitstream Format is used to store system information, rather than the content of items in the system @@ -47,7 +47,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject * String representing this Bitstream Format's file extension */ @autoserialize - extensions: string; + extensions: string[]; /** * Identifier for this Bitstream Format diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6550435aa3..5bf6260dc7 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -87,6 +87,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing import { ClaimedTaskDataService } from './tasks/claimed-task-data.service'; import { PoolTaskDataService } from './tasks/pool-task-data.service'; import { TaskResponseParsingService } from './tasks/task-response-parsing.service'; +import { BitstreamFormatDataService } from './data/bitstream-format-data.service'; const IMPORTS = [ CommonModule, @@ -126,6 +127,7 @@ const PROVIDERS = [ ObjectCacheService, PaginationComponentOptions, RegistryService, + BitstreamFormatDataService, NormalizedObjectBuildService, RemoteDataBuildService, RequestService, diff --git a/src/app/core/data/bitstream-format-data.service.spec.ts b/src/app/core/data/bitstream-format-data.service.spec.ts new file mode 100644 index 0000000000..f3ce478236 --- /dev/null +++ b/src/app/core/data/bitstream-format-data.service.spec.ts @@ -0,0 +1,293 @@ +import { BitstreamFormatDataService } from './bitstream-format-data.service'; +import { RequestEntry } from './request.reducer'; +import { RestResponse } from '../cache/response.models'; +import { Observable, of as observableOf } from 'rxjs'; +import { Action, Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { BitstreamFormat } from '../shared/bitstream-format.model'; +import { async } from '@angular/core/testing'; +import { + BitstreamFormatsRegistryDeselectAction, + BitstreamFormatsRegistryDeselectAllAction, + BitstreamFormatsRegistrySelectAction +} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions'; +import { TestScheduler } from 'rxjs/testing'; + +describe('BitstreamFormatDataService', () => { + let service: BitstreamFormatDataService; + let requestService; + let scheduler: TestScheduler; + + const bitstreamFormatsEndpoint = 'https://rest.api/core/bitstream-formats'; + const bitstreamFormatsIdEndpoint = 'https://rest.api/core/bitstream-formats/format-id'; + + const responseCacheEntry = new RequestEntry(); + responseCacheEntry.response = new RestResponse(true, 200, 'Success'); + responseCacheEntry.completed = true; + + const store = { + dispatch(action: Action) { + // Do Nothing + } + } as Store; + + const objectCache = {} as ObjectCacheService; + const halEndpointService = { + getEndpoint(linkPath: string): Observable { + return cold('a', {a: bitstreamFormatsEndpoint}); + } + } as HALEndpointService; + + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as NormalizedObjectBuildService; + const rdbService = {} as RemoteDataBuildService; + + function initTestService(halService) { + return new BitstreamFormatDataService( + requestService, + rdbService, + dataBuildService, + store, + objectCache, + halService, + notificationsService, + http, + comparator + ); + } + + describe('getBrowseEndpoint', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + })); + it('should get the browse endpoint', () => { + const result = service.getBrowseEndpoint(); + const expected = cold('b', {b: bitstreamFormatsEndpoint}); + + expect(result).toBeObservable(expected); + }); + }); + + describe('getUpdateEndpoint', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + })); + it('should get the update endpoint', () => { + const formatId = 'format-id'; + + const result = service.getUpdateEndpoint(formatId); + const expected = cold('b', {b: bitstreamFormatsIdEndpoint}); + + expect(result).toBeObservable(expected); + }); + }); + + describe('getCreateEndpoint', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + })); + it('should get the create endpoint ', () => { + + const result = service.getCreateEndpoint(); + const expected = cold('b', {b: bitstreamFormatsEndpoint}); + + expect(result).toBeObservable(expected); + }); + }); + + describe('updateBitstreamFormat', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + })); + it('should update the bitstream format', () => { + const updatedBistreamFormat = new BitstreamFormat(); + updatedBistreamFormat.uuid = 'updated-uuid'; + + const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')}); + const result = service.updateBitstreamFormat(updatedBistreamFormat); + + expect(result).toBeObservable(expected); + + }); + }); + + describe('createBitstreamFormat', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + })); + it('should create a new bitstream format', () => { + const newFormat = new BitstreamFormat(); + newFormat.uuid = 'new-uuid'; + + const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')}); + const result = service.createBitstreamFormat(newFormat); + + expect(result).toBeObservable(expected); + }); + }); + + describe('clearBitStreamFormatRequests', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + const halService = { + getEndpoint(linkPath: string): Observable { + return observableOf(bitstreamFormatsEndpoint); + } + } as HALEndpointService; + service = initTestService(halService); + service.clearBitStreamFormatRequests().subscribe(); + })); + it('should remove the bitstream format hrefs in the request service', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(bitstreamFormatsEndpoint); + }); + }); + + describe('selectBitstreamFormat', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + spyOn(store, 'dispatch'); + })); + it('should add a selected bitstream to the store', () => { + const format = new BitstreamFormat(); + format.uuid = 'uuid'; + + service.selectBitstreamFormat(format); + expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format)); + }); + }); + + describe('deselectBitstreamFormat', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + spyOn(store, 'dispatch'); + })); + it('should remove a bitstream from the store', () => { + const format = new BitstreamFormat(); + format.uuid = 'uuid'; + + service.deselectBitstreamFormat(format); + expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format)); + }); + }); + + describe('deselectAllBitstreamFormats', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: cold('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + service = initTestService(halEndpointService); + spyOn(store, 'dispatch'); + + })); + it('should remove all bitstreamFormats from the store', () => { + service.deselectAllBitstreamFormats(); + expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction()); + }); + }); + + describe('delete', () => { + beforeEach(async(() => { + scheduler = getTestScheduler(); + requestService = jasmine.createSpyObj('requestService', { + configure: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: hot('a', {a: responseCacheEntry}), + generateRequestId: 'request-id', + removeByHrefSubstring: {} + }); + const halService = { + getEndpoint(linkPath: string): Observable { + return observableOf(bitstreamFormatsEndpoint); + } + } as HALEndpointService; + service = initTestService(halService); + })); + it('should delete a bitstream format', () => { + const format = new BitstreamFormat(); + format.uuid = 'format-uuid'; + format.id = 'format-id'; + + const expected = cold('(b|)', {b: true}); + const result = service.delete(format); + + expect(result).toBeObservable(expected); + }); + }); +}); diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts new file mode 100644 index 0000000000..a5638183c0 --- /dev/null +++ b/src/app/core/data/bitstream-format-data.service.ts @@ -0,0 +1,183 @@ +import { Injectable } from '@angular/core'; +import { DataService } from './data.service'; +import { BitstreamFormat } from '../shared/bitstream-format.model'; +import { RequestService } from './request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { createSelector, select, Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; +import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models'; +import { Observable } from 'rxjs'; +import { find, map, tap } from 'rxjs/operators'; +import { configureRequest, getResponseFromEntry } from '../shared/operators'; +import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged'; +import { RestResponse } from '../cache/response.models'; +import { AppState } from '../../app.reducer'; +import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers'; +import { + BitstreamFormatsRegistryDeselectAction, + BitstreamFormatsRegistryDeselectAllAction, + BitstreamFormatsRegistrySelectAction +} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions'; +import { hasValue } from '../../shared/empty.util'; +import { RequestEntry } from './request.reducer'; + +const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats; +const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector, + (bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats); + +/** + * A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint + */ +@Injectable() +export class BitstreamFormatDataService extends DataService { + + protected linkPath = 'bitstreamformats'; + protected forceBypassCache = false; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected dataBuildService: NormalizedObjectBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } + + /** + * Get the endpoint for browsing bitstream formats + * @param {FindAllOptions} options + * @returns {Observable} + */ + getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable { + return this.halService.getEndpoint(this.linkPath); + } + + /** + * Get the endpoint to update an existing bitstream format + * @param formatId + */ + public getUpdateEndpoint(formatId: string): Observable { + return this.getBrowseEndpoint().pipe( + map((endpoint: string) => this.getIDHref(endpoint, formatId)) + ); + } + + /** + * Get the endpoint to create a new bitstream format + */ + public getCreateEndpoint(): Observable { + return this.getBrowseEndpoint(); + } + + /** + * Update an existing bitstreamFormat + * @param bitstreamFormat + */ + updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable { + const requestId = this.requestService.generateRequestId(); + + this.getUpdateEndpoint(bitstreamFormat.id).pipe( + distinctUntilChanged(), + map((endpointURL: string) => + new PutRequest(requestId, endpointURL, bitstreamFormat)), + configureRequest(this.requestService)).subscribe(); + + return this.requestService.getByUUID(requestId).pipe( + getResponseFromEntry() + ); + + } + + /** + * Create a new BitstreamFormat + * @param BitstreamFormat + */ + public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable { + const requestId = this.requestService.generateRequestId(); + + this.getCreateEndpoint().pipe( + map((endpointURL: string) => { + return new PostRequest(requestId, endpointURL, bitstreamFormat); + }), + configureRequest(this.requestService) + ).subscribe(); + + return this.requestService.getByUUID(requestId).pipe( + getResponseFromEntry() + ); + } + + /** + * Clears the cache of the list of BitstreamFormats + */ + public clearBitStreamFormatRequests(): Observable { + return this.getBrowseEndpoint().pipe( + tap((href: string) => this.requestService.removeByHrefSubstring(href)) + ); + } + + /** + * Gets all the selected BitstreamFormats from the store + */ + public getSelectedBitstreamFormats(): Observable { + return this.store.pipe(select(selectedBitstreamFormatSelector)); + } + + /** + * Adds a BistreamFormat to the selected BitstreamFormats in the store + * @param bitstreamFormat + */ + public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) { + this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat)); + } + + /** + * Removes a BistreamFormat from the list of selected BitstreamFormats in the store + * @param bitstreamFormat + */ + public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) { + this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat)); + } + + /** + * Removes all BitstreamFormats from the list of selected BitstreamFormats in the store + */ + public deselectAllBitstreamFormats() { + this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction()); + } + + /** + * Delete an existing DSpace Object on the server + * @param format The DSpace Object to be removed + * Return an observable that emits true when the deletion was successful, false when it failed + */ + delete(format: BitstreamFormat): Observable { + const requestId = this.requestService.generateRequestId(); + + const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getIDHref(endpoint, format.id))); + + hrefObs.pipe( + find((href: string) => hasValue(href)), + map((href: string) => { + const request = new DeleteByIDRequest(requestId, href, format.id); + this.requestService.configure(request); + }) + ).subscribe(); + + return this.requestService.getByUUID(requestId).pipe( + find((request: RequestEntry) => request.completed), + map((request: RequestEntry) => request.response.isSuccessful) + ); + } +} diff --git a/src/app/core/registry/mock-bitstream-format.model.ts b/src/app/core/registry/mock-bitstream-format.model.ts deleted file mode 100644 index f5811e367c..0000000000 --- a/src/app/core/registry/mock-bitstream-format.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class BitstreamFormat { - shortDescription: string; - description: string; - mimetype: string; - supportLevel: number; - internal: boolean; - extensions: string; -} diff --git a/src/app/core/registry/registry-bitstreamformats-response.model.ts b/src/app/core/registry/registry-bitstreamformats-response.model.ts index 81de379e9e..a0ebd542f8 100644 --- a/src/app/core/registry/registry-bitstreamformats-response.model.ts +++ b/src/app/core/registry/registry-bitstreamformats-response.model.ts @@ -1,10 +1,10 @@ import { autoserialize, autoserializeAs } from 'cerialize'; import { PageInfo } from '../shared/page-info.model'; -import { BitstreamFormat } from '../shared/bitstream-format.model'; +import { NormalizedBitstreamFormat } from '../cache/models/normalized-bitstream-format.model'; export class RegistryBitstreamformatsResponse { - @autoserializeAs(BitstreamFormat) - bitstreamformats: BitstreamFormat[]; + @autoserializeAs(NormalizedBitstreamFormat) + bitstreamformats: NormalizedBitstreamFormat[]; @autoserialize page: PageInfo; diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts index 8274ceef60..80e4e404c8 100644 --- a/src/app/core/registry/registry.service.spec.ts +++ b/src/app/core/registry/registry.service.spec.ts @@ -12,7 +12,6 @@ import { PageInfo } from '../shared/page-info.model'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { - RegistryBitstreamformatsSuccessResponse, RegistryMetadatafieldsSuccessResponse, RegistryMetadataschemasSuccessResponse, RestResponse @@ -20,7 +19,6 @@ import { import { Component } from '@angular/core'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; -import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model'; import { map } from 'rxjs/operators'; import { Store, StoreModule } from '@ngrx/store'; import { MockStore } from '../../shared/testing/mock-store'; @@ -42,7 +40,7 @@ import { import { MetadataSchema } from '../metadata/metadataschema.model'; import { MetadataField } from '../metadata/metadatafield.model'; -@Component({ template: '' }) +@Component({template: ''}) class DummyComponent { } @@ -119,7 +117,7 @@ describe('RegistryService', () => { toRemoteDataObservable: (requestEntryObs: Observable, payloadObs: Observable) => { return observableCombineLatest(requestEntryObs, payloadObs).pipe(map(([req, pay]) => { - return { req, pay }; + return {req, pay}; }) ); }, @@ -135,11 +133,11 @@ describe('RegistryService', () => { DummyComponent ], providers: [ - { provide: RequestService, useValue: getMockRequestService() }, - { provide: RemoteDataBuildService, useValue: rdbStub }, - { provide: HALEndpointService, useValue: halServiceStub }, - { provide: Store, useClass: MockStore }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + {provide: RequestService, useValue: getMockRequestService()}, + {provide: RemoteDataBuildService, useValue: rdbStub}, + {provide: HALEndpointService, useValue: halServiceStub}, + {provide: Store, useClass: MockStore}, + {provide: NotificationsService, useValue: new NotificationsServiceStub()}, RegistryService ] }); @@ -154,7 +152,7 @@ describe('RegistryService', () => { page: pageInfo }); const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo); - const responseEntry = Object.assign(new RequestEntry(), { response: response }); + const responseEntry = Object.assign(new RequestEntry(), {response: response}); beforeEach(() => { (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry)); @@ -183,7 +181,7 @@ describe('RegistryService', () => { page: pageInfo }); const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo); - const responseEntry = Object.assign(new RequestEntry(), { response: response }); + const responseEntry = Object.assign(new RequestEntry(), {response: response}); beforeEach(() => { (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry)); @@ -212,7 +210,7 @@ describe('RegistryService', () => { page: pageInfo }); const response = new RegistryMetadatafieldsSuccessResponse(queryResponse, 200, 'OK', pageInfo); - const responseEntry = Object.assign(new RequestEntry(), { response: response }); + const responseEntry = Object.assign(new RequestEntry(), {response: response}); beforeEach(() => { (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry)); @@ -235,35 +233,6 @@ describe('RegistryService', () => { }); }); - describe('when requesting bitstreamformats', () => { - const queryResponse = Object.assign(new RegistryBitstreamformatsResponse(), { - bitstreamformats: mockFieldsList, - page: pageInfo - }); - const response = new RegistryBitstreamformatsSuccessResponse(queryResponse, 200, 'OK', pageInfo); - const responseEntry = Object.assign(new RequestEntry(), { response: response }); - - beforeEach(() => { - (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry)); - /* tslint:disable:no-empty */ - registryService.getBitstreamFormats(pagination).subscribe((value) => { - }); - /* tslint:enable:no-empty */ - }); - - it('should call getEndpoint on the halService', () => { - expect((registryService as any).halService.getEndpoint).toHaveBeenCalled(); - }); - - it('should send out the request on the request service', () => { - expect((registryService as any).requestService.configure).toHaveBeenCalled(); - }); - - it('should call getByHref on the request service with the correct request url', () => { - expect((registryService as any).requestService.getByHref).toHaveBeenCalledWith(endpointWithParams); - }); - }); - describe('when dispatching to the store', () => { beforeEach(() => { spyOn(mockStore, 'dispatch'); @@ -276,7 +245,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0])); - }) + }); }); describe('when calling cancelEditMetadataSchema', () => { @@ -286,7 +255,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryCancelSchemaAction', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction()); - }) + }); }); describe('when calling selectMetadataSchema', () => { @@ -296,7 +265,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0])); - }) + }); }); describe('when calling deselectMetadataSchema', () => { @@ -306,7 +275,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0])); - }) + }); }); describe('when calling deselectAllMetadataSchema', () => { @@ -316,7 +285,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction()); - }) + }); }); describe('when calling editMetadataField', () => { @@ -326,7 +295,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0])); - }) + }); }); describe('when calling cancelEditMetadataField', () => { @@ -336,7 +305,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryCancelFieldAction', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction()); - }) + }); }); describe('when calling selectMetadataField', () => { @@ -346,7 +315,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0])); - }) + }); }); describe('when calling deselectMetadataField', () => { @@ -356,7 +325,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0])); - }) + }); }); describe('when calling deselectAllMetadataField', () => { @@ -366,7 +335,7 @@ describe('RegistryService', () => { it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => { expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction()); - }) + }); }); }); @@ -409,7 +378,7 @@ describe('RegistryService', () => { result.subscribe((response: RestResponse) => { expect(response.isSuccessful).toBe(true); }); - }) + }); }); describe('when deleteMetadataField is called', () => { @@ -423,7 +392,7 @@ describe('RegistryService', () => { result.subscribe((response: RestResponse) => { expect(response.isSuccessful).toBe(true); }); - }) + }); }); describe('when clearMetadataSchemaRequests is called', () => { diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index 137b4c3a87..35706c3200 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -5,13 +5,13 @@ import { PaginatedList } from '../data/paginated-list'; import { PageInfo } from '../shared/page-info.model'; import { MetadataSchema } from '../metadata/metadataschema.model'; import { MetadataField } from '../metadata/metadatafield.model'; -import { BitstreamFormat } from './mock-bitstream-format.model'; import { CreateMetadataFieldRequest, CreateMetadataSchemaRequest, DeleteRequest, GetRequest, - RestRequest, UpdateMetadataFieldRequest, + RestRequest, + UpdateMetadataFieldRequest, UpdateMetadataSchemaRequest } from '../data/request.models'; import { GenericConstructor } from '../shared/generic-constructor'; @@ -21,24 +21,19 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { RequestService } from '../data/request.service'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; import { - ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse, - RegistryBitstreamformatsSuccessResponse, + MetadatafieldSuccessResponse, + MetadataschemaSuccessResponse, RegistryMetadatafieldsSuccessResponse, - RegistryMetadataschemasSuccessResponse, RestResponse + RegistryMetadataschemasSuccessResponse, + RestResponse } from '../cache/response.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; -import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; +import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service'; -import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model'; -import { - configureRequest, - getResponseFromEntry, - getSucceededRemoteData -} from '../shared/operators'; +import { configureRequest, getResponseFromEntry } from '../shared/operators'; import { createSelector, select, Store } from '@ngrx/store'; import { AppState } from '../../app.reducer'; import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers'; @@ -54,7 +49,7 @@ import { MetadataRegistrySelectFieldAction, MetadataRegistrySelectSchemaAction } from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions'; -import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, take, tap } from 'rxjs/operators'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { ResourceType } from '../shared/resource-type'; @@ -76,7 +71,8 @@ export class RegistryService { private metadataSchemasPath = 'metadataschemas'; private metadataFieldsPath = 'metadatafields'; - private bitstreamFormatsPath = 'bitstreamformats'; + + // private bitstreamFormatsPath = 'bitstreamformats'; constructor(protected requestService: RequestService, private rdb: RemoteDataBuildService, @@ -181,7 +177,7 @@ export class RegistryService { */ public getAllMetadataFields(pagination?: PaginationComponentOptions): Observable>> { if (hasNoValue(pagination)) { - pagination = { currentPage: 1, pageSize: 10000 } as any; + pagination = {currentPage: 1, pageSize: 10000} as any; } const requestObs = this.getMetadataFieldsRequestObs(pagination); @@ -215,36 +211,6 @@ export class RegistryService { return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } - public getBitstreamFormats(pagination: PaginationComponentOptions): Observable>> { - const requestObs = this.getBitstreamFormatsRequestObs(pagination); - - const requestEntryObs = requestObs.pipe( - flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) - ); - - const rbrObs: Observable = requestEntryObs.pipe( - getResponseFromEntry(), - map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse) - ); - - const bitstreamformatsObs: Observable = rbrObs.pipe( - map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats) - ); - - const pageInfoObs: Observable = requestEntryObs.pipe( - getResponseFromEntry(), - map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo) - ); - - const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs).pipe( - map(([bitstreamformats, pageInfo]) => { - return new PaginatedList(pageInfo, bitstreamformats); - }) - ); - - return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); - } - public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable { return this.halService.getEndpoint(this.metadataSchemasPath).pipe( map((url: string) => { @@ -307,26 +273,6 @@ export class RegistryService { ); } - private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable { - return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe( - map((url: string) => { - const args: string[] = []; - args.push(`size=${pagination.pageSize}`); - args.push(`page=${pagination.currentPage - 1}`); - if (isNotEmpty(args)) { - url = new URLCombiner(url, `?${args.join('&')}`).toString(); - } - const request = new GetRequest(this.requestService.generateRequestId(), url); - return Object.assign(request, { - getResponseParser(): GenericConstructor { - return RegistryBitstreamformatsResponseParsingService; - } - }); - }), - tap((request: RestRequest) => this.requestService.configure(request)), - ); - } - public editMetadataSchema(schema: MetadataSchema) { this.store.dispatch(new MetadataRegistryEditSchemaAction(schema)); } @@ -340,15 +286,15 @@ export class RegistryService { } public selectMetadataSchema(schema: MetadataSchema) { - this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema)) + this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema)); } public deselectMetadataSchema(schema: MetadataSchema) { - this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema)) + this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema)); } public deselectAllMetadataSchema() { - this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction()) + this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction()); } public getSelectedMetadataSchemas(): Observable { @@ -368,15 +314,15 @@ export class RegistryService { } public selectMetadataField(field: MetadataField) { - this.store.dispatch(new MetadataRegistrySelectFieldAction(field)) + this.store.dispatch(new MetadataRegistrySelectFieldAction(field)); } public deselectMetadataField(field: MetadataField) { - this.store.dispatch(new MetadataRegistryDeselectFieldAction(field)) + this.store.dispatch(new MetadataRegistryDeselectFieldAction(field)); } public deselectAllMetadataField() { - this.store.dispatch(new MetadataRegistryDeselectAllFieldAction()) + this.store.dispatch(new MetadataRegistryDeselectAllFieldAction()); } public getSelectedMetadataFields(): Observable { @@ -431,7 +377,7 @@ export class RegistryService { this.notificationsService.error('Server Error:', (response as any).errorMessage, new NotificationOptions(-1)); } } else { - this.showNotifications(true, isUpdate, false, { prefix: schema.prefix }); + this.showNotifications(true, isUpdate, false, {prefix: schema.prefix}); return response; } }), @@ -451,7 +397,7 @@ export class RegistryService { public clearMetadataSchemaRequests(): Observable { return this.halService.getEndpoint(this.metadataSchemasPath).pipe( tap((href: string) => this.requestService.removeByHrefSubstring(href)) - ) + ); } /** @@ -501,7 +447,7 @@ export class RegistryService { } } else { const fieldString = `${field.schema.prefix}.${field.element}${field.qualifier ? `.${field.qualifier}` : ''}`; - this.showNotifications(true, isUpdate, true, { field: fieldString }); + this.showNotifications(true, isUpdate, true, {field: fieldString}); return response; } }), @@ -521,7 +467,7 @@ export class RegistryService { public clearMetadataFieldRequests(): Observable { return this.halService.getEndpoint(this.metadataFieldsPath).pipe( tap((href: string) => this.requestService.removeByHrefSubstring(href)) - ) + ); } private delete(path: string, id: number): Observable { @@ -557,9 +503,9 @@ export class RegistryService { ); messages.subscribe(([head, content]) => { if (success) { - this.notificationsService.success(head, content) + this.notificationsService.success(head, content); } else { - this.notificationsService.error(head, content) + this.notificationsService.error(head, content); } }); } diff --git a/src/app/core/shared/bitstream-format-support-level.ts b/src/app/core/shared/bitstream-format-support-level.ts new file mode 100644 index 0000000000..d92aac7708 --- /dev/null +++ b/src/app/core/shared/bitstream-format-support-level.ts @@ -0,0 +1,5 @@ +export enum BitstreamFormatSupportLevel { + Known = 'KNOWN', + Unknown = 'UNKNOWN', + Supported = 'SUPPORTED' +} diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts index 9af345e607..aa76f92b36 100644 --- a/src/app/core/shared/bitstream-format.model.ts +++ b/src/app/core/shared/bitstream-format.model.ts @@ -1,6 +1,6 @@ - import { CacheableObject } from '../cache/object-cache.reducer'; import { ResourceType } from './resource-type'; +import { BitstreamFormatSupportLevel } from './bitstream-format-support-level'; /** * Model class for a Bitstream Format @@ -25,7 +25,7 @@ export class BitstreamFormat implements CacheableObject { /** * The level of support the system offers for this Bitstream Format */ - supportLevel: number; + supportLevel: BitstreamFormatSupportLevel; /** * True if the Bitstream Format is used to store system information, rather than the content of items in the system @@ -35,7 +35,7 @@ export class BitstreamFormat implements CacheableObject { /** * String representing this Bitstream Format's file extension */ - extensions: string; + extensions: string[]; /** * The link to the rest endpoint where this Bitstream Format can be found @@ -52,4 +52,11 @@ export class BitstreamFormat implements CacheableObject { */ uuid: string; + /** + * Identifier for this Bitstream Format + * Note that this ID is unique for bitstream formats, + * but might not be unique across different object types + */ + id: string; + } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index cead04f797..217f9e79cf 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -14,7 +14,7 @@ - +
{{ message | translate:model.validators }} From 2db0bf44f30233091bc5cfff2023751faaaec0f1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 12 Aug 2019 10:22:49 +0200 Subject: [PATCH 35/60] 62741: PR Feedback changes --- .../journal-issue-grid-element.component.ts | 3 +++ .../journal-volume-grid-element.component.ts | 3 +++ .../journal/journal-grid-element.component.ts | 3 +++ .../orgunit/orgunit-grid-element.component.ts | 3 +++ .../person/person-grid-element.component.html | 10 +++++----- .../person/person-grid-element.component.ts | 3 +++ .../project/project-grid-element.component.ts | 3 +++ .../publication/publication-grid-element.component.ts | 3 +++ 8 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts index 538971bf84..06c27ebacf 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts @@ -10,5 +10,8 @@ import { TypedItemSearchResultGridElementComponent } from '../../../../shared/ob templateUrl: './journal-issue-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Journal Issue + */ export class JournalIssueGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts index 3ef4e9948e..e5183536ef 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts @@ -10,5 +10,8 @@ import { TypedItemSearchResultGridElementComponent } from '../../../../shared/ob templateUrl: './journal-volume-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Journal Volume + */ export class JournalVolumeGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts index 37c0b0aad1..7f23211538 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts @@ -10,5 +10,8 @@ import { TypedItemSearchResultGridElementComponent } from '../../../../shared/ob templateUrl: './journal-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Journal + */ export class JournalGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts index f0c87eb975..0effc22027 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts @@ -10,5 +10,8 @@ import { TypedItemSearchResultGridElementComponent } from '../../../../shared/ob templateUrl: './orgunit-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Organisation Unit + */ export class OrgunitGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index badc241b65..92d1ed40a8 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -9,16 +9,16 @@
-

+

- -

+

- +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts index 3ec17c9ce5..bf7b8aa119 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts @@ -10,5 +10,8 @@ import { focusShadow } from '../../../../shared/animations/focus'; templateUrl: './person-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Person + */ export class PersonGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts index be246f0fbe..15d525fcf2 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts @@ -10,5 +10,8 @@ import { TypedItemSearchResultGridElementComponent } from '../../../../shared/ob templateUrl: './project-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Project + */ export class ProjectGridElementComponent extends TypedItemSearchResultGridElementComponent { } diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts index 18dcccd1d2..1bcd028baf 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts @@ -11,5 +11,8 @@ import { focusShadow } from '../../../../animations/focus'; templateUrl: './publication-grid-element.component.html', animations: [focusShadow] }) +/** + * The component for displaying a grid element for an item of the type Publication + */ export class PublicationGridElementComponent extends TypedItemSearchResultGridElementComponent { } From 1be7f4e55078d7f1a26834af06f6017389f2dc15 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 12 Aug 2019 11:05:45 +0200 Subject: [PATCH 36/60] 62741: Grid template field changes --- .../journal-issue-grid-element.component.html | 4 ++-- .../journal-issue-grid-element.component.spec.ts | 2 +- .../journal-volume-grid-element.component.html | 8 ++++---- ...journal-volume-grid-element.component.spec.ts | 4 ++-- .../journal/journal-grid-element.component.html | 14 ++++++++------ .../journal-grid-element.component.spec.ts | 6 +++--- .../orgunit/orgunit-grid-element.component.html | 16 +++++++++------- .../orgunit-grid-element.component.spec.ts | 6 +++--- .../person/person-grid-element.component.html | 4 ++-- .../person/person-grid-element.component.spec.ts | 4 ++-- .../project/project-grid-element.component.html | 4 ++-- .../project-grid-element.component.spec.ts | 6 +++--- 12 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html index 93d3954f2f..4cb34a140b 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -11,9 +11,9 @@

-

+

- +

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts index 3af407d0f8..68a05b66c3 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts @@ -15,7 +15,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'journalissue.issuedate': [ + 'creativework.datePublished': [ { language: null, value: '2015-06-26' diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html index f5487a34cf..d7c9b68a24 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -11,14 +11,14 @@

-

+

- +

-

+

- +

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts index 4751ed4cd8..5a8fca5fc6 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts @@ -15,13 +15,13 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'journalvolume.issuedate': [ + 'creativework.datePublished': [ { language: null, value: '2015-06-26' } ], - 'journalvolume.identifier.description': [ + 'dc.description': [ { language: 'en_US', value: 'A description for the journal volume' diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html index d4354efcf4..467cdd1594 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -11,17 +11,19 @@

-

- {{dso.firstMetadataValue('journal.contributor.editor')}} - , - {{dso.firstMetadataValue('journal.publisher')}} + {{dso.firstMetadataValue('creativework.editor')}} + + , + {{dso.firstMetadataValue('creativework.publisher')}} +

-

+

- +

diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts index d9934e2d2f..8c12c1a266 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts @@ -15,19 +15,19 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'journal.contributor.editor': [ + 'creativework.editor': [ { language: 'en_US', value: 'Smith, Donald' } ], - 'journal.publisher': [ + 'creativework.publisher': [ { language: 'en_US', value: 'A company' } ], - 'journal.identifier.description': [ + 'dc.description': [ { language: 'en_US', value: 'This is the description' diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index 92fffd0166..104d3a0a57 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -9,19 +9,21 @@
-

+

-

+

- +

-

- {{dso.firstMetadataValue('orgunit.identifier.country')}} - , - {{dso.firstMetadataValue('orgunit.identifier.city')}} + {{dso.firstMetadataValue('organization.address.addressCountry')}} + + , + {{dso.firstMetadataValue('organization.address.addressLocality')}} +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts index 25249fd2b0..39ddea4c7b 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts @@ -15,19 +15,19 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'orgunit.identifier.dateestablished': [ + 'organization.foundingDate': [ { language: null, value: '2015-06-26' } ], - 'orgunit.identifier.country': [ + 'organization.address.addressCountry': [ { language: 'en_US', value: 'Belgium' } ], - 'orgunit.identifier.city': [ + 'organization.address.addressLocality': [ { language: 'en_US', value: 'Brussels' diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index 92d1ed40a8..86353377fa 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -16,9 +16,9 @@

-

+

- +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts index b3343a0605..a0f8e4c29e 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts @@ -15,13 +15,13 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'person.identifier.email': [ + 'person.email': [ { language: 'en_US', value: 'Smith-Donald@gmail.com' } ], - 'person.identifier.jobtitle': [ + 'person.jobTitle': [ { language: 'en_US', value: 'Web Developer' diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index 1fe4d18dae..a595791cc4 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -11,9 +11,9 @@

-

+

- +

diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts index bcf19ed96a..9ad26935b7 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts @@ -15,10 +15,10 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { value: 'This is just another title' } ], - 'project.identifier.funder': [ + 'dc.description': [ { language: 'en_US', - value: 'The project funder' + value: 'The project description' } ] } @@ -38,4 +38,4 @@ mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { } }); -describe('ProjectGridElementComponent', getEntityGridElementTestComponent(ProjectGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['funder'])); +describe('ProjectGridElementComponent', getEntityGridElementTestComponent(ProjectGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['description'])); From e05f44d079f406cad7fbc31c5bc4e14bde143e72 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 13 Aug 2019 12:30:47 +0200 Subject: [PATCH 37/60] 64225: Import fix --- .../edit-item-page/item-metadata/item-metadata.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index dbbcebfd00..be657d71dc 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -15,10 +15,10 @@ import { NotificationsService } from '../../../shared/notifications/notification import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { TranslateService } from '@ngx-translate/core'; import { RegistryService } from '../../../core/registry/registry.service'; -import { MetadataField } from '../../../core/metadata/metadatafield.model'; import { MetadatumViewModel } from '../../../core/shared/metadata.models'; import { Metadata } from '../../../core/shared/metadata.utils'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; +import { MetadataField } from '../../../core/metadata/metadata-field.model'; @Component({ selector: 'ds-item-metadata', From fc21dc2019ee79f887b81b0006b1a1f238161e18 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 13 Aug 2019 14:46:10 +0200 Subject: [PATCH 38/60] 64225: Search results reloading correctly after object deletion --- src/app/+search-page/search-page.component.ts | 2 +- .../search-service/search.service.ts | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 03433d1da1..c268c5b7f6 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -109,7 +109,7 @@ export class SearchPageComponent implements OnInit { ngOnInit(): void { this.searchOptions$ = this.getSearchOptions(); this.sub = this.searchOptions$.pipe( - switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(observableOf(undefined))))) + switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(undefined)))) .subscribe((results) => { this.resultsRD$.next(results); }); diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 4e67fda864..be95ed096e 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -103,11 +103,18 @@ export class SearchService implements OnDestroy { * @returns {Observable>>>} Emits a paginated list with all search results found */ search(searchOptions?: PaginatedSearchOptions): Observable>>> { - const requestObs = this.halService.getEndpoint(this.searchLinkPath).pipe( + const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe( map((url: string) => { if (hasValue(searchOptions)) { - url = (searchOptions as PaginatedSearchOptions).toRestUrl(url); + return (searchOptions as PaginatedSearchOptions).toRestUrl(url); + } else { + return url; } + }) + ); + + const requestObs = hrefObs.pipe( + map((url: string) => { const request = new this.request(this.requestService.generateRequestId(), url); const getResponseParserFn: () => GenericConstructor = () => { @@ -169,11 +176,20 @@ export class SearchService implements OnDestroy { const payloadObs = observableCombineLatest(tDomainListObs, pageInfoObs).pipe( map(([tDomainList, pageInfo]) => { - return new PaginatedList(pageInfo, tDomainList); + return new PaginatedList(pageInfo, tDomainList.filter((obj) => hasValue(obj))); }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); + return observableCombineLatest(hrefObs, tDomainListObs, requestEntryObs).pipe( + switchMap(([href, tDomainList, requestEntry]) => { + if (tDomainList.indexOf(undefined) > -1 && requestEntry && requestEntry.completed) { + this.requestService.removeByHrefSubstring(href); + return this.search(searchOptions) + } else { + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); + } + }) + ); } /** From 930da6dde376cea1de837134b8a335fef914dad8 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 13 Aug 2019 15:13:51 +0200 Subject: [PATCH 39/60] fix the labels on edit collection and community pages --- .../comcol-forms/comcol-form/comcol-form.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts index 6a96892b06..8d1d5c1dca 100644 --- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts @@ -9,6 +9,7 @@ import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynami import { TranslateService } from '@ngx-translate/core'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models'; +import { ResourceType } from '../../../core/shared/resource-type'; import { isNotEmpty } from '../../empty.util'; import { Community } from '../../../core/shared/community.model'; @@ -29,7 +30,7 @@ export class ComColFormComponent implements OnInit { /** * Type of DSpaceObject that the form represents */ - protected type; + protected type: ResourceType; /** * @type {string} Key prefix used to generate form labels @@ -110,11 +111,11 @@ export class ComColFormComponent implements OnInit { private updateFieldTranslations() { this.formModel.forEach( (fieldModel: DynamicInputModel) => { - fieldModel.label = this.translate.instant(this.type + this.LABEL_KEY_PREFIX + fieldModel.id); + fieldModel.label = this.translate.instant(this.type.value + this.LABEL_KEY_PREFIX + fieldModel.id); if (isNotEmpty(fieldModel.validators)) { fieldModel.errorMessages = {}; Object.keys(fieldModel.validators).forEach((key) => { - fieldModel.errorMessages[key] = this.translate.instant(this.type + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); + fieldModel.errorMessages[key] = this.translate.instant(this.type.value + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); }); } } From 295bbc5c402e79b91129110c183aacc13ff42f7c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 14 Aug 2019 11:22:36 +0200 Subject: [PATCH 40/60] flatten en.json --- resources/i18n/en.json | 1863 +++++++++++++++------------------------- 1 file changed, 690 insertions(+), 1173 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 642c1db1ba..c5b542c424 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -1,1175 +1,692 @@ { - "footer": { - "copyright": "copyright © 2002-{{ year }}", - "link.dspace": "DSpace software", - "link.duraspace": "DuraSpace" - }, - "collection": { - "page": { - "news": "News", - "license": "License", - "browse": { - "recent": { - "head": "Recent Submissions" - } - } - }, - "form": { - "title": "Name", - "description": "Introductory text (HTML)", - "abstract": "Short Description", - "rights": "Copyright text (HTML)", - "tableofcontents": "News (HTML)", - "license": "License", - "provenance": "Provenance", - "errors": { - "title": { - "required": "Please enter a collection name" - } - } - }, - "edit": { - "head": "Edit Collection", - "delete": "Delete this collection" - }, - "create": { - "head": "Create a Collection", - "sub-head": "Create a Collection for Community {{ parent }}" - }, - "delete": { - "head": "Delete Collection", - "text": "Are you sure you want to delete collection \"{{ dso }}\"", - "confirm": "Confirm", - "cancel": "Cancel", - "notification": { - "success": "Successfully deleted collection", - "fail": "Collection could not be deleted" - } - } - }, - "community": { - "page": { - "news": "News", - "license": "License" - }, - "sub-collection-list": { - "head": "Collections of this Community" - }, - "sub-community-list": { - "head": "Communities of this Community" - }, - "form": { - "title": "Name", - "description": "Introductory text (HTML)", - "abstract": "Short Description", - "rights": "Copyright text (HTML)", - "tableofcontents": "News (HTML)", - "errors": { - "title": { - "required": "Please enter a community name" - } - } - }, - "edit": { - "head": "Edit Community", - "delete": "Delete this community" - }, - "create": { - "head": "Create a Community", - "sub-head": "Create a Sub-Community for Community {{ parent }}" - }, - "delete": { - "head": "Delete Community", - "text": "Are you sure you want to delete community \"{{ dso }}\"", - "confirm": "Confirm", - "cancel": "Cancel", - "notification": { - "success": "Successfully deleted community", - "fail": "Community could not be deleted" - } - } - }, - "item": { - "page": { - "author": "Authors", - "abstract": "Abstract", - "date": "Date", - "uri": "URI", - "files": "Files", - "collections": "Collections", - "subject": "Keywords", - "citation": "Citation", - "filesection": { - "download": "Download", - "name": "Name:", - "format": "Format:", - "size": "Size:", - "description": "Description:" - }, - "link": { - "simple": "Simple item page", - "full": "Full item page" - }, - "journal": { - "search": { - "title": "Articles in this journal" - } - }, - "person": { - "search": { - "title": "Articles by this author" - } - } - }, - "select": { - "table": { - "collection": "Collection", - "author": "Author", - "title": "Title" - }, - "confirm": "Confirm selected" - }, - "edit": { - "head": "Edit Item", - "tabs": { - "status": { - "head": "Item Status", - "description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", - "title": "Item Edit - Status", - "labels": { - "id": "Item Internal ID", - "handle": "Handle", - "lastModified": "Last Modified", - "itemPage": "Item Page" - }, - "buttons": { - "authorizations": { - "label": "Edit item's authorization policies", - "button": "Authorizations..." - }, - "withdraw": { - "label": "Withdraw item from the repository", - "button": "Withdraw..." - }, - "reinstate": { - "label": "Reinstate item into the repository", - "button": "Reinstate..." - }, - "move": { - "label": "Move item to another collection", - "button": "Move..." - }, - "private": { - "label": "Make item private", - "button": "Make it private..." - }, - "public": { - "label": "Make item public", - "button": "Make it public..." - }, - "delete": { - "label": "Completely expunge item", - "button": "Permanently delete" - }, - "mappedCollections": { - "label": "Manage mapped collections", - "button": "Mapped collections" - } - } - }, - "bitstreams": { - "head": "Item Bitstreams", - "title": "Item Edit - Bitstreams" - }, - "metadata": { - "head": "Item Metadata", - "title": "Item Edit - Metadata" - }, - "view": { - "head": "View Item", - "title": "Item Edit - View" - }, - "curate": { - "head": "Curate", - "title": "Item Edit - Curate" - } - }, - "modify.overview": { - "field": "Field", - "value": "Value", - "language": "Language" - }, - "withdraw": { - "header": "Withdraw item: {{ id }}", - "description": "Are you sure this item should be withdrawn from the archive?", - "confirm": "Withdraw", - "cancel": "Cancel", - "success": "The item was withdrawn successfully", - "error": "An error occurred while withdrawing the item" - }, - "reinstate": { - "header": "Reinstate item: {{ id }}", - "description": "Are you sure this item should be reinstated to the archive?", - "confirm": "Reinstate", - "cancel": "Cancel", - "success": "The item was reinstated successfully", - "error": "An error occurred while reinstating the item" - }, - "private": { - "header": "Make item private: {{ id }}", - "description": "Are you sure this item should be made private in the archive?", - "confirm": "Make it Private", - "cancel": "Cancel", - "success": "The item is now private", - "error": "An error occurred while making the item private" - }, - "public": { - "header": "Make item public: {{ id }}", - "description": "Are you sure this item should be made public in the archive?", - "confirm": "Make it Public", - "cancel": "Cancel", - "success": "The item is now public", - "error": "An error occurred while making the item public" - }, - "delete": { - "header": "Delete item: {{ id }}", - "description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.", - "confirm": "Delete", - "cancel": "Cancel", - "success": "The item has been deleted", - "error": "An error occurred while deleting the item" - }, - "metadata": { - "add-button": "Add", - "discard-button": "Discard", - "reinstate-button": "Undo", - "save-button": "Save", - "headers": { - "field": "Field", - "value": "Value", - "language": "Lang", - "edit": "Edit" - }, - "edit": { - "buttons": { - "edit": "Edit", - "unedit": "Stop editing", - "remove": "Remove", - "undo": "Undo changes" - } - }, - "metadatafield": { - "invalid": "Please choose a valid metadata field" - }, - "notifications": { - "outdated": { - "title": "Changed outdated", - "content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts" - }, - "discarded": { - "title": "Changed discarded", - "content": "Your changes were discarded. To reinstate your changes click the 'Undo' button" - }, - "invalid": { - "title": "Metadata invalid", - "content": "Your changes were not saved. Please make sure all fields are valid before you save." - }, - "saved": { - "title": "Metadata saved", - "content": "Your changes to this item's metadata were saved." - } - } - } - } - }, - "relationships": { - "isPublicationOf": "Publications", - "isProjectOf": "Research Projects", - "isOrgUnitOf": "Organizational Units", - "isAuthorOf": "Authors", - "isPersonOf": "Authors", - "isJournalOf": "Journals", - "isSingleJournalOf": "Journal", - "isVolumeOf": "Journal Volumes", - "isSingleVolumeOf": "Journal Volume", - "isIssueOf": "Journal Issues", - "isJournalIssueOf": "Journal Issue", - "isPublicationOfJournalIssue": "Articles" - }, - "person": { - "page": { - "titleprefix": "Person: ", - "jobtitle": "Job Title", - "lastname": "Last Name", - "firstname": "First Name", - "email": "Email Address", - "orcid": "ORCID", - "birthdate": "Birth Date", - "staffid": "Staff ID", - "link": { - "full": "Show all metadata" - } - }, - "listelement": { - "badge": "Person" - }, - "search": { - "title": "DSpace Angular :: Person Search", - "results": { - "head": "Person Search Results" - } - } - }, - "project": { - "page": { - "titleprefix": "Research Project: ", - "status": "Status", - "contributor": "Contributors", - "funder": "Funders", - "id": "ID", - "expectedcompletion": "Expected Completion", - "description": "Description", - "keyword": "Keywords" - }, - "listelement": { - "badge": "Research Project" - } - }, - "orgunit": { - "page": { - "titleprefix": "Organizational Unit: ", - "dateestablished": "Date established", - "city": "City", - "country": "Country", - "id": "ID", - "description": "Description" - }, - "listelement": { - "badge": "Organizational Unit" - } - }, - "journal": { - "page": { - "titleprefix": "Journal: ", - "issn": "ISSN", - "publisher": "Publisher", - "description": "Description", - "editor": "Editor-in-Chief" - }, - "listelement": { - "badge": "Journal" - }, - "search": { - "title": "DSpace Angular :: Journal Search", - "results": { - "head": "Journal Search Results" - } - } - }, - "journalvolume": { - "page": { - "titleprefix": "Journal Volume: ", - "volume": "Volume", - "issuedate": "Issue Date", - "description": "Description" - }, - "listelement": { - "badge": "Journal Volume" - } - }, - "journalissue": { - "page": { - "titleprefix": "Journal Issue: ", - "number": "Number", - "issuedate": "Issue Date", - "description": "Description", - "keyword": "Keywords", - "journal-title": "Journal Title", - "journal-issn": "Journal ISSN" - }, - "listelement": { - "badge": "Journal Issue" - } - }, - "publication": { - "page": { - "titleprefix": "Publication: ", - "journal-title": "Journal Title", - "journal-issn": "Journal ISSN", - "volume-title": "Volume Title", - "publisher": "Publisher", - "description": "Description" - - }, - "listelement": { - "badge": "Publication" - }, - "search": { - "title": "DSpace Angular :: Publication Search", - "results": { - "head": "Publication Search Results" - } - } - }, - "nav": { - "browse": { - "header": "All of DSpace" - }, - "community-browse": { - "header": "By Community" - }, - "statistics": { - "header": "Statistics" - }, - "login": "Log In", - "logout": "Log Out", - "mydspace": "MyDSpace", - "language": "Language switch", - "search": "Search" - }, - "pagination": { - "results-per-page": "Results Per Page", - "sort-direction": "Sort Options", - "showing": { - "label": "Now showing ", - "detail": "{{ range }} of {{ total }}" - } - }, - "sorting": { - "score": { - "DESC": "Relevance" - }, - "dc.title": { - "ASC": "Title Ascending", - "DESC": "Title Descending" - } - }, + "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", + "404.link.home-page": "Take me to the home page", + "404.page-not-found": "page not found", + "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.", + "admin.registries.bitstream-formats.create.failure.head": "Failure", + "admin.registries.bitstream-formats.create.head": "Create Bitstream format", + "admin.registries.bitstream-formats.create.new": "Add a new bitstream format", + "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.", + "admin.registries.bitstream-formats.create.success.head": "Success", + "admin.registries.bitstream-formats.delete.failure.amount": "The following amount of bitstream formats could not be deleted: {{ amount }} ", + "admin.registries.bitstream-formats.delete.failure.head": "Failure", + "admin.registries.bitstream-formats.delete.success.amount": "The following amount of bitstream formats have been successfully deleted: {{ amount }}", + "admin.registries.bitstream-formats.delete.success.head": "Success", + "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.", + "admin.registries.bitstream-formats.edit.description.hint": "", + "admin.registries.bitstream-formats.edit.description.label": "Description", + "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.", + "admin.registries.bitstream-formats.edit.extensions.label": "File extensions", + "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.", + "admin.registries.bitstream-formats.edit.failure.head": "Failure", + "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}", + "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.", + "admin.registries.bitstream-formats.edit.internal.label": "Internal", + "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.", + "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type", + "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)", + "admin.registries.bitstream-formats.edit.shortDescription.label": "Name", + "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.", + "admin.registries.bitstream-formats.edit.success.head": "Success", + "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.", + "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level", + "admin.registries.bitstream-formats.head": "Bitstream Format Registry", + "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.", + "admin.registries.bitstream-formats.table.delete": "Delete selected", + "admin.registries.bitstream-formats.table.deselect-all": "Deselect all", + "admin.registries.bitstream-formats.table.internal": "internal", + "admin.registries.bitstream-formats.table.mimetype": "MIME Type", + "admin.registries.bitstream-formats.table.name": "Name", + "admin.registries.bitstream-formats.table.return": "Return", + "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known", + "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported", + "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown", + "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level", + "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry", + "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", + "admin.registries.metadata.form.create": "Create metadata schema", + "admin.registries.metadata.form.edit": "Edit metadata schema", + "admin.registries.metadata.form.name": "Name", + "admin.registries.metadata.form.namespace": "Namespace", + "admin.registries.metadata.head": "Metadata Registry", + "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.", + "admin.registries.metadata.schemas.table.delete": "Delete selected", + "admin.registries.metadata.schemas.table.id": "ID", + "admin.registries.metadata.schemas.table.name": "Name", + "admin.registries.metadata.schemas.table.namespace": "Namespace", + "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry", + "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".", + "admin.registries.schema.fields.head": "Schema metadata fields", + "admin.registries.schema.fields.no-items": "No metadata fields to show.", + "admin.registries.schema.fields.table.delete": "Delete selected", + "admin.registries.schema.fields.table.field": "Field", + "admin.registries.schema.fields.table.scopenote": "Scope Note", + "admin.registries.schema.form.create": "Create metadata field", + "admin.registries.schema.form.edit": "Edit metadata field", + "admin.registries.schema.form.element": "Element", + "admin.registries.schema.form.qualifier": "Qualifier", + "admin.registries.schema.form.scopenote": "Scope Note", + "admin.registries.schema.head": "Metadata Schema", + "admin.registries.schema.notification.created": "Successfully created metadata schema \"{{prefix}}\"", + "admin.registries.schema.notification.deleted.failure": "Failed to delete {{amount}} metadata schemas", + "admin.registries.schema.notification.deleted.success": "Successfully deleted {{amount}} metadata schemas", + "admin.registries.schema.notification.edited": "Successfully edited metadata schema \"{{prefix}}\"", + "admin.registries.schema.notification.failure": "Error", + "admin.registries.schema.notification.field.created": "Successfully created metadata field \"{{field}}\"", + "admin.registries.schema.notification.field.deleted.failure": "Failed to delete {{amount}} metadata fields", + "admin.registries.schema.notification.field.deleted.success": "Successfully deleted {{amount}} metadata fields", + "admin.registries.schema.notification.field.edited": "Successfully edited metadata field \"{{field}}\"", + "admin.registries.schema.notification.success": "Success", + "admin.registries.schema.return": "Return", + "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry", + "auth.errors.invalid-user": "Invalid email address or password.", + "auth.messages.expired": "Your session has expired. Please log in again.", + "browse.comcol.by.author": "By Author", + "browse.comcol.by.dateissued": "By Issue Date", + "browse.comcol.by.subject": "By Subject", + "browse.comcol.by.title": "By Title", + "browse.comcol.head": "Browse", + "browse.empty": "No items to show.", + "browse.metadata.author": "Author", + "browse.metadata.dateissued": "Issue Date", + "browse.metadata.subject": "Subject", + "browse.metadata.title": "Title", + "browse.startsWith.choose_start": "(Choose start)", + "browse.startsWith.choose_year": "(Choose year)", + "browse.startsWith.jump": "Jump to a point in the index:", + "browse.startsWith.months.april": "April", + "browse.startsWith.months.august": "August", + "browse.startsWith.months.december": "December", + "browse.startsWith.months.february": "February", + "browse.startsWith.months.january": "January", + "browse.startsWith.months.july": "July", + "browse.startsWith.months.june": "June", + "browse.startsWith.months.march": "March", + "browse.startsWith.months.may": "May", + "browse.startsWith.months.none": "(Choose month)", + "browse.startsWith.months.november": "November", + "browse.startsWith.months.october": "October", + "browse.startsWith.months.september": "September", + "browse.startsWith.submit": "Go", + "browse.startsWith.type_date": "Or type in a date (year-month):", + "browse.startsWith.type_text": "Or enter first few letters:", + "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}", + "chips.remove": "Remove chip", + "collection.create.head": "Create a Collection", + "collection.create.sub-head": "Create a Collection for Community {{ parent }}", + "collection.delete.cancel": "Cancel", + "collection.delete.confirm": "Confirm", + "collection.delete.head": "Delete Collection", + "collection.delete.notification.fail": "Collection could not be deleted", + "collection.delete.notification.success": "Successfully deleted collection", + "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", + "collection.edit.delete": "Delete this collection", + "collection.edit.head": "Edit Collection", + "collection.form.abstract": "Short Description", + "collection.form.description": "Introductory text (HTML)", + "collection.form.errors.title.required": "Please enter a collection name", + "collection.form.license": "License", + "collection.form.provenance": "Provenance", + "collection.form.rights": "Copyright text (HTML)", + "collection.form.tableofcontents": "News (HTML)", + "collection.form.title": "Name", + "collection.page.browse.recent.head": "Recent Submissions", + "collection.page.license": "License", + "collection.page.news": "News", + "community.create.head": "Create a Community", + "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", + "community.delete.cancel": "Cancel", + "community.delete.confirm": "Confirm", + "community.delete.head": "Delete Community", + "community.delete.notification.fail": "Community could not be deleted", + "community.delete.notification.success": "Successfully deleted community", + "community.delete.text": "Are you sure you want to delete community \"{{ dso }}\"", + "community.edit.delete": "Delete this community", + "community.edit.head": "Edit Community", + "community.form.abstract": "Short Description", + "community.form.description": "Introductory text (HTML)", + "community.form.errors.title.required": "Please enter a community name", + "community.form.rights": "Copyright text (HTML)", + "community.form.tableofcontents": "News (HTML)", + "community.form.title": "Name", + "community.page.license": "License", + "community.page.news": "News", + "community.sub-collection-list.head": "Collections of this Community", + "community.sub-community-list.head": "Communities of this Community", + "dso-selector.create.collection.head": "New collection", + "dso-selector.create.community.head": "New community", + "dso-selector.create.community.sub-level": "Create a new community in", + "dso-selector.create.community.top-level": "Create a new top-level community", + "dso-selector.create.item.head": "New item", + "dso-selector.edit.collection.head": "Edit collection", + "dso-selector.edit.community.head": "Edit community", + "dso-selector.edit.item.head": "Edit item", + "dso-selector.no-results": "No {{ type }} found", + "dso-selector.placeholder": "Search for a {{ type }}", + "error.browse-by": "Error fetching items", + "error.collection": "Error fetching collection", + "error.community": "Error fetching community", + "error.default": "Error", + "error.item": "Error fetching item", + "error.objects": "Error fetching objects", + "error.recent-submissions": "Error fetching recent submissions", + "error.search-results": "Error fetching search results", + "error.sub-collections": "Error fetching sub-collections", + "error.sub-communities": "Error fetching sub-communities", + "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :

", + "error.top-level-communities": "Error fetching top-level communities", + "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", + "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", + "footer.copyright": "copyright © 2002-{{ year }}", + "footer.link.dspace": "DSpace software", + "footer.link.duraspace": "DuraSpace", + "form.cancel": "Cancel", + "form.clear": "Clear", + "form.clear-help": "Click here to remove the selected value", + "form.edit": "Edit", + "form.edit-help": "Click here to edit the selected value", + "form.first-name": "First name", + "form.group-collapse": "Collapse", + "form.group-collapse-help": "Click here to collapse", + "form.group-expand": "Expand", + "form.group-expand-help": "Click here to expand and add more elements", + "form.last-name": "Last name", + "form.loading": "Loading...", + "form.no-results": "No results found", + "form.no-value": "No value entered", + "form.other-information": {}, + "form.remove": "Remove", + "form.save": "Save", + "form.save-help": "Save changes", + "form.search": "Search", + "form.search-help": "Click here to looking for an existing correspondence", + "form.submit": "Submit", + "home.description": "", + "home.title": "DSpace Angular :: Home", + "home.top-level-communities.head": "Communities in DSpace", + "home.top-level-communities.help": "Select a community to browse its collections.", + "item.edit.delete.cancel": "Cancel", + "item.edit.delete.confirm": "Delete", + "item.edit.delete.description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.", + "item.edit.delete.error": "An error occurred while deleting the item", + "item.edit.delete.header": "Delete item: {{ id }}", + "item.edit.delete.success": "The item has been deleted", + "item.edit.head": "Edit Item", + "item.edit.metadata.add-button": "Add", + "item.edit.metadata.discard-button": "Discard", + "item.edit.metadata.edit.buttons.edit": "Edit", + "item.edit.metadata.edit.buttons.remove": "Remove", + "item.edit.metadata.edit.buttons.undo": "Undo changes", + "item.edit.metadata.edit.buttons.unedit": "Stop editing", + "item.edit.metadata.headers.edit": "Edit", + "item.edit.metadata.headers.field": "Field", + "item.edit.metadata.headers.language": "Lang", + "item.edit.metadata.headers.value": "Value", + "item.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field", + "item.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", + "item.edit.metadata.notifications.discarded.title": "Changed discarded", + "item.edit.metadata.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", + "item.edit.metadata.notifications.invalid.title": "Metadata invalid", + "item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", + "item.edit.metadata.notifications.outdated.title": "Changed outdated", + "item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.", + "item.edit.metadata.notifications.saved.title": "Metadata saved", + "item.edit.metadata.reinstate-button": "Undo", + "item.edit.metadata.save-button": "Save", + "item.edit.modify.overview.field": "Field", + "item.edit.modify.overview.language": "Language", + "item.edit.modify.overview.value": "Value", + "item.edit.private.cancel": "Cancel", + "item.edit.private.confirm": "Make it Private", + "item.edit.private.description": "Are you sure this item should be made private in the archive?", + "item.edit.private.error": "An error occurred while making the item private", + "item.edit.private.header": "Make item private: {{ id }}", + "item.edit.private.success": "The item is now private", + "item.edit.public.cancel": "Cancel", + "item.edit.public.confirm": "Make it Public", + "item.edit.public.description": "Are you sure this item should be made public in the archive?", + "item.edit.public.error": "An error occurred while making the item public", + "item.edit.public.header": "Make item public: {{ id }}", + "item.edit.public.success": "The item is now public", + "item.edit.reinstate.cancel": "Cancel", + "item.edit.reinstate.confirm": "Reinstate", + "item.edit.reinstate.description": "Are you sure this item should be reinstated to the archive?", + "item.edit.reinstate.error": "An error occurred while reinstating the item", + "item.edit.reinstate.header": "Reinstate item: {{ id }}", + "item.edit.reinstate.success": "The item was reinstated successfully", + "item.edit.tabs.bitstreams.head": "Item Bitstreams", + "item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams", + "item.edit.tabs.curate.head": "Curate", + "item.edit.tabs.curate.title": "Item Edit - Curate", + "item.edit.tabs.metadata.head": "Item Metadata", + "item.edit.tabs.metadata.title": "Item Edit - Metadata", + "item.edit.tabs.status.buttons.authorizations.button": "Authorizations...", + "item.edit.tabs.status.buttons.authorizations.label": "Edit item's authorization policies", + "item.edit.tabs.status.buttons.delete.button": "Permanently delete", + "item.edit.tabs.status.buttons.delete.label": "Completely expunge item", + "item.edit.tabs.status.buttons.mappedCollections.button": "Mapped collections", + "item.edit.tabs.status.buttons.mappedCollections.label": "Manage mapped collections", + "item.edit.tabs.status.buttons.move.button": "Move...", + "item.edit.tabs.status.buttons.move.label": "Move item to another collection", + "item.edit.tabs.status.buttons.private.button": "Make it private...", + "item.edit.tabs.status.buttons.private.label": "Make item private", + "item.edit.tabs.status.buttons.public.button": "Make it public...", + "item.edit.tabs.status.buttons.public.label": "Make item public", + "item.edit.tabs.status.buttons.reinstate.button": "Reinstate...", + "item.edit.tabs.status.buttons.reinstate.label": "Reinstate item into the repository", + "item.edit.tabs.status.buttons.withdraw.button": "Withdraw...", + "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", + "item.edit.tabs.status.description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", + "item.edit.tabs.status.head": "Item Status", + "item.edit.tabs.status.labels.handle": "Handle", + "item.edit.tabs.status.labels.id": "Item Internal ID", + "item.edit.tabs.status.labels.itemPage": "Item Page", + "item.edit.tabs.status.labels.lastModified": "Last Modified", + "item.edit.tabs.status.title": "Item Edit - Status", + "item.edit.tabs.view.head": "View Item", + "item.edit.tabs.view.title": "Item Edit - View", + "item.edit.withdraw.cancel": "Cancel", + "item.edit.withdraw.confirm": "Withdraw", + "item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?", + "item.edit.withdraw.error": "An error occurred while withdrawing the item", + "item.edit.withdraw.header": "Withdraw item: {{ id }}", + "item.edit.withdraw.success": "The item was withdrawn successfully", + "item.page.abstract": "Abstract", + "item.page.author": "Authors", + "item.page.citation": "Citation", + "item.page.collections": "Collections", + "item.page.date": "Date", + "item.page.files": "Files", + "item.page.filesection.description": "Description:", + "item.page.filesection.download": "Download", + "item.page.filesection.format": "Format:", + "item.page.filesection.name": "Name:", + "item.page.filesection.size": "Size:", + "item.page.journal.search.title": "Articles in this journal", + "item.page.link.full": "Full item page", + "item.page.link.simple": "Simple item page", + "item.page.person.search.title": "Articles by this author", + "item.page.subject": "Keywords", + "item.page.uri": "URI", + "item.select.confirm": "Confirm selected", + "item.select.table.author": "Author", + "item.select.table.collection": "Collection", + "item.select.table.title": "Title", + "journal.listelement.badge": "Journal", + "journal.page.description": "Description", + "journal.page.editor": "Editor-in-Chief", + "journal.page.issn": "ISSN", + "journal.page.publisher": "Publisher", + "journal.page.titleprefix": "Journal: ", + "journal.search.results.head": "Journal Search Results", + "journal.search.title": "DSpace Angular :: Journal Search", + "journalissue.listelement.badge": "Journal Issue", + "journalissue.page.description": "Description", + "journalissue.page.issuedate": "Issue Date", + "journalissue.page.journal-issn": "Journal ISSN", + "journalissue.page.journal-title": "Journal Title", + "journalissue.page.keyword": "Keywords", + "journalissue.page.number": "Number", + "journalissue.page.titleprefix": "Journal Issue: ", + "journalvolume.listelement.badge": "Journal Volume", + "journalvolume.page.description": "Description", + "journalvolume.page.issuedate": "Issue Date", + "journalvolume.page.titleprefix": "Journal Volume: ", + "journalvolume.page.volume": "Volume", + "loading.browse-by": "Loading items...", + "loading.browse-by-page": "Loading page...", + "loading.collection": "Loading collection...", + "loading.community": "Loading community...", + "loading.default": "Loading...", + "loading.item": "Loading item...", + "loading.mydspace-results": "Loading items...", + "loading.objects": "Loading...", + "loading.recent-submissions": "Loading recent submissions...", + "loading.search-results": "Loading search results...", + "loading.sub-collections": "Loading sub-collections...", + "loading.sub-communities": "Loading sub-communities...", + "loading.top-level-communities": "Loading top-level communities...", + "login.form.email": "Email address", + "login.form.forgot-password": "Have you forgotten your password?", + "login.form.header": "Please log in to DSpace", + "login.form.new-user": "New user? Click here to register.", + "login.form.password": "Password", + "login.form.submit": "Log in", + "login.title": "Login", + "logout.form.header": "Log out from DSpace", + "logout.form.submit": "Log out", + "logout.title": "Logout", + "menu.header.admin": "Admin", + "menu.header.image.logo": "Repository logo", + "menu.section.access_control": "Access Control", + "menu.section.access_control_authorizations": "Authorizations", + "menu.section.access_control_groups": "Groups", + "menu.section.access_control_people": "People", + "menu.section.browse_community": "This Community", + "menu.section.browse_community_by_author": "By Author", + "menu.section.browse_community_by_issue_date": "By Issue Date", + "menu.section.browse_community_by_title": "By Title", + "menu.section.browse_global": "All of DSpace", + "menu.section.browse_global_by_author": "By Author", + "menu.section.browse_global_by_dateissued": "By Issue Date", + "menu.section.browse_global_by_subject": "By Subject", + "menu.section.browse_global_by_title": "By Title", + "menu.section.browse_global_communities_and_collections": "Communities & Collections", + "menu.section.control_panel": "Control Panel", + "menu.section.curation_task": "Curation Task", + "menu.section.edit": "Edit", + "menu.section.edit_collection": "Collection", + "menu.section.edit_community": "Community", + "menu.section.edit_item": "Item", + "menu.section.export": "Export", + "menu.section.export_collection": "Collection", + "menu.section.export_community": "Community", + "menu.section.export_item": "Item", + "menu.section.export_metadata": "Metadata", + "menu.section.find": "Find", + "menu.section.find_items": "Items", + "menu.section.find_private_items": "Private Items", + "menu.section.find_withdrawn_items": "Withdrawn Items", + "menu.section.icon.access_control": "Access Control menu section", + "menu.section.icon.control_panel": "Control Panel menu section", + "menu.section.icon.curation_task": "Curation Task menu section", + "menu.section.icon.edit": "Edit menu section", + "menu.section.icon.export": "Export menu section", + "menu.section.icon.find": "Find menu section", + "menu.section.icon.import": "Import menu section", + "menu.section.icon.new": "New menu section", + "menu.section.icon.pin": "Pin sidebar", + "menu.section.icon.registries": "Registries menu section", + "menu.section.icon.statistics_task": "Statistics Task menu section", + "menu.section.icon.unpin": "Unpin sidebar", + "menu.section.import": "Import", + "menu.section.import_batch": "Batch Import (ZIP)", + "menu.section.import_metadata": "Metadata", + "menu.section.new": "New", + "menu.section.new_collection": "Collection", + "menu.section.new_community": "Community", + "menu.section.new_item": "Item", + "menu.section.new_item_version": "Item Version", + "menu.section.pin": "Pin sidebar", + "menu.section.registries": "Registries", + "menu.section.registries_format": "Format", + "menu.section.registries_metadata": "Metadata", + "menu.section.statistics": "Statistics", + "menu.section.statistics_task": "Statistics Task", + "menu.section.toggle.access_control": "Toggle Access Control section", + "menu.section.toggle.control_panel": "Toggle Control Panel section", + "menu.section.toggle.curation_task": "Toggle Curation Task section", + "menu.section.toggle.edit": "Toggle Edit section", + "menu.section.toggle.export": "Toggle Export section", + "menu.section.toggle.find": "Toggle Find section", + "menu.section.toggle.import": "Toggle Import section", + "menu.section.toggle.new": "Toggle New section", + "menu.section.toggle.registries": "Toggle Registries section", + "menu.section.toggle.statistics_task": "Toggle Statistics Task section", + "menu.section.unpin": "Unpin sidebar", + "mydspace.description": "", + "mydspace.general.text-here": "HERE", + "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", + "mydspace.messages.description-placeholder": "Insert your message here...", + "mydspace.messages.hide-msg": "Hide message", + "mydspace.messages.mark-as-read": "Mark as read", + "mydspace.messages.mark-as-unread": "Mark as unread", + "mydspace.messages.no-content": "No content.", + "mydspace.messages.no-messages": "No messages yet.", + "mydspace.messages.send-btn": "Send", + "mydspace.messages.show-msg": "Show message", + "mydspace.messages.subject-placeholder": "Subject...", + "mydspace.messages.submitter-help": "Select this option to send a message to controller.", + "mydspace.messages.title": "Messages", + "mydspace.messages.to": "To", + "mydspace.new-submission": "New submission", + "mydspace.results.head": "Your submissions", + "mydspace.results.no-abstract": "No Abstract", + "mydspace.results.no-authors": "No Authors", + "mydspace.results.no-collections": "No Collections", + "mydspace.results.no-date": "No Date", + "mydspace.results.no-files": "No Files", + "mydspace.results.no-results": "There were no items to show", + "mydspace.results.no-title": "No title", + "mydspace.results.no-uri": "No Uri", + "mydspace.show.workflow": "All tasks", + "mydspace.show.workspace": "Your Submissions", + "mydspace.status.archived": "Archived", + "mydspace.status.validation": "Validation", + "mydspace.status.waiting-for-controller": "Waiting for controller", + "mydspace.status.workflow": "Workflow", + "mydspace.status.workspace": "Workspace", + "mydspace.title": "MyDSpace", + "mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.", + "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", + "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", + "mydspace.view-btn": "View", + "nav.browse.header": "All of DSpace", + "nav.community-browse.header": "By Community", + "nav.language": "Language switch", + "nav.login": "Log In", + "nav.logout": "Log Out", + "nav.mydspace": "MyDSpace", + "nav.search": "Search", + "nav.statistics.header": "Statistics", + "orgunit.listelement.badge": "Organizational Unit", + "orgunit.page.city": "City", + "orgunit.page.country": "Country", + "orgunit.page.dateestablished": "Date established", + "orgunit.page.description": "Description", + "orgunit.page.id": "ID", + "orgunit.page.titleprefix": "Organizational Unit: ", + "pagination.results-per-page": "Results Per Page", + "pagination.showing.detail": "{{ range }} of {{ total }}", + "pagination.showing.label": "Now showing ", + "pagination.sort-direction": "Sort Options", + "person.listelement.badge": "Person", + "person.page.birthdate": "Birth Date", + "person.page.email": "Email Address", + "person.page.firstname": "First Name", + "person.page.jobtitle": "Job Title", + "person.page.lastname": "Last Name", + "person.page.link.full": "Show all metadata", + "person.page.orcid": "ORCID", + "person.page.staffid": "Staff ID", + "person.page.titleprefix": "Person: ", + "person.search.results.head": "Person Search Results", + "person.search.title": "DSpace Angular :: Person Search", + "project.listelement.badge": "Research Project", + "project.page.contributor": "Contributors", + "project.page.description": "Description", + "project.page.expectedcompletion": "Expected Completion", + "project.page.funder": "Funders", + "project.page.id": "ID", + "project.page.keyword": "Keywords", + "project.page.status": "Status", + "project.page.titleprefix": "Research Project: ", + "publication.listelement.badge": "Publication", + "publication.page.description": "Description", + "publication.page.journal-issn": "Journal ISSN", + "publication.page.journal-title": "Journal Title", + "publication.page.publisher": "Publisher", + "publication.page.titleprefix": "Publication: ", + "publication.page.volume-title": "Volume Title", + "publication.search.results.head": "Publication Search Results", + "publication.search.title": "DSpace Angular :: Publication Search", + "relationships.isAuthorOf": "Authors", + "relationships.isIssueOf": "Journal Issues", + "relationships.isJournalIssueOf": "Journal Issue", + "relationships.isJournalOf": "Journals", + "relationships.isOrgUnitOf": "Organizational Units", + "relationships.isPersonOf": "Authors", + "relationships.isProjectOf": "Research Projects", + "relationships.isPublicationOf": "Publications", + "relationships.isPublicationOfJournalIssue": "Articles", + "relationships.isSingleJournalOf": "Journal", + "relationships.isSingleVolumeOf": "Journal Volume", + "relationships.isVolumeOf": "Journal Volumes", + "search.description": "", + "search.filters.applied.f.author": "Author", + "search.filters.applied.f.dateIssued.max": "End date", + "search.filters.applied.f.dateIssued.min": "Start date", + "search.filters.applied.f.dateSubmitted": "Date submitted", + "search.filters.applied.f.entityType": "Item Type", + "search.filters.applied.f.has_content_in_original_bundle": "Has files", + "search.filters.applied.f.itemtype": "Type", + "search.filters.applied.f.namedresourcetype": "Status", + "search.filters.applied.f.subject": "Subject", + "search.filters.applied.f.submitter": "Submitter", + "search.filters.filter.author.head": "Author", + "search.filters.filter.author.placeholder": "Author name", + "search.filters.filter.birthDate.head": "Birth Date", + "search.filters.filter.birthDate.placeholder": "Birth Date", + "search.filters.filter.creativeDatePublished.head": "Date Published", + "search.filters.filter.creativeDatePublished.placeholder": "Date Published", + "search.filters.filter.creativeWorkEditor.head": "Editor", + "search.filters.filter.creativeWorkEditor.placeholder": "Editor", + "search.filters.filter.creativeWorkKeywords.head": "Subject", + "search.filters.filter.creativeWorkKeywords.placeholder": "Subject", + "search.filters.filter.creativeWorkPublisher.head": "Publisher", + "search.filters.filter.creativeWorkPublisher.placeholder": "Publisher", + "search.filters.filter.dateIssued.head": "Date", + "search.filters.filter.dateIssued.max.placeholder": "Minimum Date", + "search.filters.filter.dateIssued.min.placeholder": "Maximum Date", + "search.filters.filter.dateSubmitted.head": "Date submitted", + "search.filters.filter.dateSubmitted.placeholder": "Date submitted", + "search.filters.filter.entityType.head": "Item Type", + "search.filters.filter.entityType.placeholder": "Item Type", + "search.filters.filter.has_content_in_original_bundle.head": "Has files", + "search.filters.filter.itemtype.head": "Type", + "search.filters.filter.itemtype.placeholder": "Type", + "search.filters.filter.jobTitle.head": "Job Title", + "search.filters.filter.jobTitle.placeholder": "Job Title", + "search.filters.filter.knowsLanguage.head": "Known language", + "search.filters.filter.knowsLanguage.placeholder": "Known language", + "search.filters.filter.namedresourcetype.head": "Status", + "search.filters.filter.namedresourcetype.placeholder": "Status", + "search.filters.filter.objectpeople.head": "People", + "search.filters.filter.objectpeople.placeholder": "People", + "search.filters.filter.organizationAddressCountry.head": "Country", + "search.filters.filter.organizationAddressCountry.placeholder": "Country", + "search.filters.filter.organizationAddressLocality.head": "City", + "search.filters.filter.organizationAddressLocality.placeholder": "City", + "search.filters.filter.organizationFoundingDate.head": "Date Founded", + "search.filters.filter.organizationFoundingDate.placeholder": "Date Founded", + "search.filters.filter.scope.head": "Scope", + "search.filters.filter.scope.placeholder": "Scope filter", + "search.filters.filter.show-less": "Collapse", + "search.filters.filter.show-more": "Show more", + "search.filters.filter.subject.head": "Subject", + "search.filters.filter.subject.placeholder": "Subject", + "search.filters.filter.submitter.head": "Submitter", + "search.filters.filter.submitter.placeholder": "Submitter", + "search.filters.head": "Filters", + "search.filters.reset": "Reset filters", + "search.form.search": "Search", + "search.form.search_dspace": "Search DSpace", + "search.form.search_mydspace": "Search MyDSpace", + "search.results.head": "Search Results", + "search.results.no-results": "Your search returned no results. Having trouble finding what you're looking for? Try putting", + "search.results.no-results-link": "quotes around it", + "search.sidebar.close": "Back to results", + "search.sidebar.filters.title": "Filters", + "search.sidebar.open": "Search Tools", + "search.sidebar.results": "results", + "search.sidebar.settings.rpp": "Results per page", + "search.sidebar.settings.sort-by": "Sort By", + "search.sidebar.settings.title": "Settings", + "search.switch-configuration.title": "Show", + "search.title": "DSpace Angular :: Search", + "search.view-switch.show-detail": "Show detail", + "search.view-switch.show-grid": "Show as grid", + "search.view-switch.show-list": "Show as list", + "sorting.dc.title.ASC": "Title Ascending", + "sorting.dc.title.DESC": "Title Descending", + "sorting.score.DESC": "Relevance", + "submission.edit.title": "Edit Submission", + "submission.general.cannot_submit": "You have not the privilege to make a new submission.", + "submission.general.deposit": "Deposit", + "submission.general.discard.confirm.cancel": "Cancel", + "submission.general.discard.confirm.info": "This operation can't be undone. Are you sure?", + "submission.general.discard.confirm.submit": "Yes, I'm sure", + "submission.general.discard.confirm.title": "Discard submission", + "submission.general.discard.submit": "Discard", + "submission.general.save": "Save", + "submission.general.save-later": "Save for later", + "submission.mydspace": {}, + "submission.sections.general.add-more": "Add more", + "submission.sections.general.collection": "Collection", + "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.", + "submission.sections.general.deposit_success_notice": "Submission deposited successfully.", + "submission.sections.general.discard_error_notice": "There was an issue when discarding the item, please try again later.", + "submission.sections.general.discard_success_notice": "Submission discarded successfully.", + "submission.sections.general.metadata-extracted": "New metadata have been extracted and added to the {{sectionId}} section.", + "submission.sections.general.metadata-extracted-new-section": "New {{sectionId}} section has been added to submission.", + "submission.sections.general.no-collection": "No collection found", + "submission.sections.general.no-sections": "No options available", + "submission.sections.general.save_error_notice": "There was an issue when saving the item, please try again later.", + "submission.sections.general.save_success_notice": "Submission saved successfully.", + "submission.sections.general.search-collection": "Search for a collection", + "submission.sections.general.sections_not_valid": "There are incomplete sections.", + "submission.sections.submit.progressbar.cclicense": "Creative commons license", + "submission.sections.submit.progressbar.describe.recycle": "Recycle", + "submission.sections.submit.progressbar.describe.stepcustom": "Describe", + "submission.sections.submit.progressbar.describe.stepone": "Describe", + "submission.sections.submit.progressbar.describe.steptwo": "Describe", + "submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates", + "submission.sections.submit.progressbar.license": "Deposit license", + "submission.sections.submit.progressbar.upload": "Upload files", + "submission.sections.upload.delete.confirm.cancel": "Cancel", + "submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?", + "submission.sections.upload.delete.confirm.submit": "Yes, I'm sure", + "submission.sections.upload.delete.confirm.title": "Delete bitstream", + "submission.sections.upload.delete.submit": "Delete", + "submission.sections.upload.drop-message": "Drop files to attach them to the item", + "submission.sections.upload.form.access-condition-label": "Access condition type", + "submission.sections.upload.form.date-required": "Date is required.", + "submission.sections.upload.form.from-label": "Access grant from", + "submission.sections.upload.form.from-placeholder": "From", + "submission.sections.upload.form.group-label": "Group", + "submission.sections.upload.form.group-required": "Group is required.", + "submission.sections.upload.form.until-label": "Access grant until", + "submission.sections.upload.form.until-placeholder": "Until", + "submission.sections.upload.header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", + "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.no-entry": "No", + "submission.sections.upload.no-file-uploaded": "No file uploaded yet.", + "submission.sections.upload.save-metadata": "Save metadata", + "submission.sections.upload.undo": "Cancel", + "submission.sections.upload.upload-failed": "Upload failed", + "submission.sections.upload.upload-successful": "Upload successful", + "submission.submit.title": "Submission", + "submission.workflow.generic.delete": "Delete", + "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", + "submission.workflow.generic.edit": "Edit", + "submission.workflow.generic.edit-help": "Select this option to change the item's metadata.", + "submission.workflow.generic.view": "View", + "submission.workflow.generic.view-help": "Select this option to view the item's metadata.", + "submission.workflow.tasks.claimed.approve": "Approve", + "submission.workflow.tasks.claimed.approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".", + "submission.workflow.tasks.claimed.edit": "Edit", + "submission.workflow.tasks.claimed.edit_help": "Select this option to change the item's metadata.", + "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", + "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", + "submission.workflow.tasks.claimed.reject.reason.submit": "Reject item", + "submission.workflow.tasks.claimed.reject.reason.title": "Reason", + "submission.workflow.tasks.claimed.reject.submit": "Reject", + "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", + "submission.workflow.tasks.claimed.return": "Return to pool", + "submission.workflow.tasks.claimed.return_help": "Return the task to the pool so that another user may perform the task.", + "submission.workflow.tasks.generic.error": "Error occurred during operation...", + "submission.workflow.tasks.generic.processing": "Processing...", + "submission.workflow.tasks.generic.submitter": "Submitter", + "submission.workflow.tasks.generic.success": "Operation successful", + "submission.workflow.tasks.pool.claim": "Claim", + "submission.workflow.tasks.pool.claim_help": "Assign this task to yourself.", + "submission.workflow.tasks.pool.hide-detail": "Hide detail", + "submission.workflow.tasks.pool.show-detail": "Show detail", "title": "DSpace", - "404": { - "help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", - "page-not-found": "page not found", - "link": { - "home-page": "Take me to the home page" - } - }, - "home": { - "title": "DSpace Angular :: Home", - "description": "", - "top-level-communities": { - "head": "Communities in DSpace", - "help": "Select a community to browse its collections." - } - }, - "mydspace": { - "title": "MyDSpace", - "description": "", - "new-submission": "New submission", - "results": { - "head": "Your submissions", - "no-results": "There were no items to show", - "no-title": "No title", - "no-authors": "No Authors", - "no-date": "No Date", - "no-abstract": "No Abstract", - "no-files": "No Files", - "no-uri": "No Uri", - "no-collections": "No Collections" - }, - "messages": { - "title": "Messages", - "to": "To", - "hide-msg": "Hide message", - "show-msg": "Show message", - "no-messages": "No messages yet.", - "no-content": "No content.", - "send-btn": "Send", - "subject-placeholder": "Subject...", - "description-placeholder": "Insert your message here...", - "mark-as-read": "Mark as read", - "mark-as-unread": "Mark as unread", - "submitter-help": "Select this option to send a message to controller.", - "controller-help": "Select this option to send a message to item's submitter." - }, - "show": { - "workspace": "Your Submissions", - "workflow": "All tasks" - }, - "status": { - "workflow": "Workflow", - "validation": "Validation", - "waiting-for-controller": "Waiting for controller", - "workspace": "Workspace", - "archived": "Archived" - }, - "view-btn": "View", - "general": { - "text-here": "HERE" - }, - "upload": { - "upload-successful": "New workspace item created. Click {{here}} for edit it.", - "upload-multiple-successful": "{{qty}} new workspace items created.", - "upload-failed": "Error creating new workspace. Please verify the content uploaded before retry." - } - }, - "search": { - "title": "DSpace Angular :: Search", - "description": "", - "form": { - "search": "Search", - "search_dspace": "Search DSpace", - "search_mydspace": "Search MyDSpace" - }, - "results": { - "head": "Search Results", - "no-results": "Your search returned no results. Having trouble finding what you're looking for? Try putting", - "no-results-link": "quotes around it" - }, - "sidebar": { - "close": "Back to results", - "open": "Search Tools", - "results": "results", - "filters": { - "title": "Filters" - }, - "settings": { - "title": "Settings", - "sort-by": "Sort By", - "rpp": "Results per page" - } - }, - "switch-configuration": { - "title":"Show" - }, - "view-switch": { - "show-list": "Show as list", - "show-grid": "Show as grid", - "show-detail": "Show detail" - }, - "filters": { - "head": "Filters", - "reset": "Reset filters", - "applied": { - "f.author": "Author", - "f.dateIssued.min": "Start date", - "f.dateIssued.max": "End date", - "f.subject": "Subject", - "f.has_content_in_original_bundle": "Has files", - "f.entityType": "Item Type", - "f.namedresourcetype": "Status", - "f.dateSubmitted": "Date submitted", - "f.itemtype": "Type", - "f.submitter": "Submitter" - }, - "filter": { - "show-more": "Show more", - "show-less": "Collapse", - "author": { - "placeholder": "Author name", - "head": "Author" - }, - "scope": { - "placeholder": "Scope filter", - "head": "Scope" - }, - "subject": { - "placeholder": "Subject", - "head": "Subject" - }, - "dateIssued": { - "max": { - "placeholder": "Minimum Date" - }, - "min": { - "placeholder": "Maximum Date" - }, - "head": "Date" - }, - "has_content_in_original_bundle": { - "head": "Has files" - }, - "entityType": { - "placeholder": "Item Type", - "head": "Item Type" - }, - "namedresourcetype": { - "placeholder": "Status", - "head": "Status" - }, - "dateSubmitted": { - "placeholder": "Date submitted", - "head": "Date submitted" - }, - "itemtype": { - "placeholder": "Type", - "head": "Type" - }, - "submitter": { - "placeholder": "Submitter", - "head": "Submitter" - }, - "objectpeople": { - "placeholder": "People", - "head": "People" - }, - "jobTitle": { - "placeholder": "Job Title", - "head": "Job Title" - }, - "knowsLanguage": { - "placeholder": "Known language", - "head": "Known language" - }, - "birthDate": { - "placeholder": "Birth Date", - "head": "Birth Date" - }, - "creativeWorkPublisher": { - "placeholder": "Publisher", - "head": "Publisher" - }, - "creativeWorkEditor": { - "placeholder": "Editor", - "head": "Editor" - }, - "creativeWorkKeywords": { - "placeholder": "Subject", - "head": "Subject" - }, - "creativeDatePublished": { - "placeholder": "Date Published", - "head": "Date Published" - }, - "organizationAddressCountry": { - "placeholder": "Country", - "head": "Country" - }, - "organizationAddressLocality": { - "placeholder": "City", - "head": "City" - }, - "organizationFoundingDate": { - "placeholder": "Date Founded", - "head": "Date Founded" - } - } - } - }, - "browse": { - "title": "Browsing {{ collection }} by {{ field }} {{ value }}", - "startsWith": { - "jump": "Jump to a point in the index:", - "choose_year": "(Choose year)", - "choose_start": "(Choose start)", - "type_date": "Or type in a date (year-month):", - "type_text": "Or enter first few letters:", - "months": { - "none": "(Choose month)", - "january": "January", - "february": "February", - "march": "March", - "april": "April", - "may": "May", - "june": "June", - "july": "July", - "august": "August", - "september": "September", - "october": "October", - "november": "November", - "december": "December" - }, - "submit": "Go" - }, - "metadata": { - "title": "Title", - "author": "Author", - "subject": "Subject", - "dateissued": "Issue Date" - }, - "comcol": { - "head": "Browse", - "by": { - "title": "By Title", - "dateissued": "By Issue Date", - "author": "By Author", - "subject": "By Subject" - } - }, - "empty": "No items to show." - }, - "admin": { - "registries": { - "metadata": { - "title": "DSpace Angular :: Metadata Registry", - "head": "Metadata Registry", - "description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", - "form": { - "create": "Create metadata schema", - "edit": "Edit metadata schema", - "namespace": "Namespace", - "name": "Name" - }, - "schemas": { - "table": { - "id": "ID", - "namespace": "Namespace", - "name": "Name", - "delete": "Delete selected" - }, - "no-items": "No metadata schemas to show." - } - }, - "schema": { - "title": "DSpace Angular :: Metadata Schema Registry", - "head": "Metadata Schema", - "description": "This is the metadata schema for \"{{namespace}}\".", - "return": "Return", - "form": { - "create": "Create metadata field", - "edit": "Edit metadata field", - "element": "Element", - "qualifier": "Qualifier", - "scopenote": "Scope Note" - }, - "fields": { - "head": "Schema metadata fields", - "table": { - "field": "Field", - "scopenote": "Scope Note", - "delete": "Delete selected" - }, - "no-items": "No metadata fields to show." - }, - "notification": { - "success": "Success", - "failure": "Error", - "created": "Successfully created metadata schema \"{{prefix}}\"", - "edited": "Successfully edited metadata schema \"{{prefix}}\"", - "deleted": { - "success": "Successfully deleted {{amount}} metadata schemas", - "failure": "Failed to delete {{amount}} metadata schemas" - }, - "field": { - "created": "Successfully created metadata field \"{{field}}\"", - "edited": "Successfully edited metadata field \"{{field}}\"", - "deleted": { - "success": "Successfully deleted {{amount}} metadata fields", - "failure": "Failed to delete {{amount}} metadata fields" - } - } - } - }, - "bitstream-formats": { - "title": "DSpace Angular :: Bitstream Format Registry", - "head": "Bitstream Format Registry", - "description": "This list of bitstream formats provides information about known formats and their support level.", - "create": { - "new": "Add a new bitstream format", - "head": "Create Bitstream format", - "success" : { - "head": "Success", - "content": "The new bitstream format was successfully created." - }, - "failure" : { - "head": "Failure", - "content": "An error occurred while creating the new bitstream format." - } - }, - "table": { - "name": "Name", - "mimetype": "MIME Type", - "supportLevel": { - "head": "Support Level", - "UNKNOWN": "Unknown", - "KNOWN": "Known", - "SUPPORTED": "Supported" - }, - "internal": "internal", - "delete": "Delete selected", - "return": "Return", - "deselect-all": "Deselect all" - }, - "no-items": "No bitstream formats to show.", - "edit": { - "head": "Bitstream format: {{ format }}", - "shortDescription": { - "label": "Name", - "hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)" - }, - "mimetype": { - "label": "MIME Type", - "hint": "The MIME type associated with this format, does not have to be unique." - }, - "description": { - "label": "Description", - "hint": "" - }, - "supportLevel": { - "label": "Support level", - "hint": "The level of support your institution pledges for this format." - }, - "internal": { - "label": "Internal", - "hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes." - }, - "extensions": { - "label": "File extensions", - "hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format." - }, - "success" : { - "head": "Success", - "content": "The bitstream format was successfully edited." - }, - "failure" : { - "head": "Failure", - "content": "An error occurred while editing the bitstream format." - } - }, - "delete": { - "success":{ - "head": "Success", - "amount": "The following amount of bitstream formats have been successfully deleted: {{ amount }}" - }, - "failure": { - "head": "Failure", - "amount": "The following amount of bitstream formats could not be deleted: {{ amount }} " - } - } - } - } - }, - "menu": { - "header": { - "admin": "Admin", - "image": { - "logo": "Repository logo" - } - }, - "section": { - "pin": "Pin sidebar", - "unpin": "Unpin sidebar", - "new": "New", - "new_community": "Community", - "new_collection": "Collection", - "new_item": "Item", - "new_item_version": "Item Version", - "edit": "Edit", - "edit_community": "Community", - "edit_collection": "Collection", - "edit_item": "Item", - "import": "Import", - "import_metadata": "Metadata", - "import_batch": "Batch Import (ZIP)", - "export": "Export", - "export_community": "Community", - "export_collection": "Collection", - "export_item": "Item", - "export_metadata": "Metadata", - "access_control": "Access Control", - "access_control_people": "People", - "access_control_groups": "Groups", - "access_control_authorizations": "Authorizations", - "find": "Find", - "find_items": "Items", - "find_withdrawn_items": "Withdrawn Items", - "find_private_items": "Private Items", - "registries": "Registries", - "registries_metadata": "Metadata", - "registries_format": "Format", - "curation_task": "Curation Task", - "statistics_task": "Statistics Task", - "control_panel": "Control Panel", - "browse_global": "All of DSpace", - "browse_global_communities_and_collections": "Communities & Collections", - "browse_global_by_dateissued": "By Issue Date", - "browse_global_by_author": "By Author", - "browse_global_by_title": "By Title", - "browse_global_by_subject": "By Subject", - "statistics": "Statistics", - "browse_community": "This Community", - "browse_community_by_issue_date": "By Issue Date", - "browse_community_by_author": "By Author", - "browse_community_by_title": "By Title", - "icon": { - "pin": "Pin sidebar", - "unpin": "Unpin sidebar", - "new": "New menu section", - "edit": "Edit menu section", - "import": "Import menu section", - "export": "Export menu section", - "access_control": "Access Control menu section", - "find": "Find menu section", - "registries": "Registries menu section", - "curation_task": "Curation Task menu section", - "statistics_task": "Statistics Task menu section", - "control_panel": "Control Panel menu section" - }, - "toggle": { - "new": "Toggle New section", - "edit": "Toggle Edit section", - "import": "Toggle Import section", - "export": "Toggle Export section", - "access_control": "Toggle Access Control section", - "find": "Toggle Find section", - "registries": "Toggle Registries section", - "curation_task": "Toggle Curation Task section", - "statistics_task": "Toggle Statistics Task section", - "control_panel": "Toggle Control Panel section" - } - } - }, - "loading": { - "default": "Loading...", - "top-level-communities": "Loading top-level communities...", - "community": "Loading community...", - "collection": "Loading collection...", - "sub-collections": "Loading sub-collections...", - "sub-communities": "Loading sub-communities...", - "recent-submissions": "Loading recent submissions...", - "item": "Loading item...", - "objects": "Loading...", - "search-results": "Loading search results...", - "mydspace-results": "Loading items...", - "browse-by": "Loading items...", - "browse-by-page": "Loading page..." - }, - "error": { - "default": "Error", - "top-level-communities": "Error fetching top-level communities", - "community": "Error fetching community", - "collection": "Error fetching collection", - "sub-collections": "Error fetching sub-collections", - "sub-communities": "Error fetching sub-communities", - "recent-submissions": "Error fetching recent submissions", - "item": "Error fetching item", - "objects": "Error fetching objects", - "search-results": "Error fetching search results", - "browse-by": "Error fetching items", - "validation": { - "pattern": "This input is restricted by the current pattern: {{ pattern }}.", - "license": { - "notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission." - } - }, - "submission": { - "sections": { - "init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :

" - } - } - }, - "form": { - "submit": "Submit", - "cancel": "Cancel", - "search": "Search", - "search-help": "Click here to looking for an existing correspondence", - "remove": "Remove", - "clear": "Clear", - "clear-help": "Click here to remove the selected value", - "edit": "Edit", - "edit-help": "Click here to edit the selected value", - "save": "Save", - "save-help": "Save changes", - "first-name": "First name", - "last-name": "Last name", - "loading": "Loading...", - "no-results": "No results found", - "no-value": "No value entered", - "group-collapse": "Collapse", - "group-expand": "Expand", - "group-collapse-help": "Click here to collapse", - "group-expand-help": "Click here to expand and add more elements", - "other-information": { - } - }, - "login": { - "title": "Login", - "form": { - "header": "Please log in to DSpace", - "email": "Email address", - "forgot-password": "Have you forgotten your password?", - "new-user": "New user? Click here to register.", - "password": "Password", - "submit": "Log in" - } - }, - "logout": { - "title": "Logout", - "form": { - "header": "Log out from DSpace", - "submit": "Log out" - } - }, - "auth": { - "messages": { - "expired": "Your session has expired. Please log in again." - }, - "errors": { - "invalid-user": "Invalid email address or password." - } - }, - "chips": { - "remove": "Remove chip" - }, - "dso-selector": { - "create": { - "community": { - "head": "New community", - "sub-level": "Create a new community in", - "top-level": "Create a new top-level community" - }, - "collection": { - "head": "New collection" - }, - "item": { - "head": "New item" - } - }, - "edit": { - "community": { - "head": "Edit community" - }, - "collection": { - "head": "Edit collection" - }, - "item": { - "head": "Edit item" - } - }, - "placeholder": "Search for a {{ type }}", - "no-results": "No {{ type }} found" - }, - "submission": { - "general":{ - "cannot_submit": "You have not the privilege to make a new submission.", - "deposit": "Deposit", - "discard": { - "submit": "Discard", - "confirm": { - "cancel": "Cancel", - "submit": "Yes, I'm sure", - "title": "Discard submission", - "info": "This operation can't be undone. Are you sure?" - } - }, - "save": "Save", - "save-later": "Save for later" - }, - "submit": { - "title": "Submission" - }, - "edit": { - "title": "Edit Submission" - }, - "mydspace": { - - }, - "sections": { - - "general": { - "add-more": "Add more", - "no-sections": "No options available", - "sections_not_valid": "There are incomplete sections.", - "collection": "Collection", - "no-collection": "No collection found", - "search-collection": "Search for a collection", - "save_error_notice": "There was an issue when saving the item, please try again later.", - "deposit_success_notice": "Submission deposited successfully.", - "deposit_error_notice": "There was an issue when submitting the item, please try again later.", - "discard_success_notice": "Submission discarded successfully.", - "discard_error_notice": "There was an issue when discarding the item, please try again later.", - "save_success_notice": "Submission saved successfully.", - "metadata-extracted": "New metadata have been extracted and added to the {{sectionId}} section.", - "metadata-extracted-new-section": "New {{sectionId}} section has been added to submission." - }, - "submit.progressbar.describe.stepone": "Describe", - "submit.progressbar.describe.steptwo": "Describe", - "submit.progressbar.describe.stepcustom": "Describe", - "submit.progressbar.describe.recycle": "Recycle", - "submit.progressbar.upload": "Upload files", - "submit.progressbar.license": "Deposit license", - "submit.progressbar.cclicense": "Creative commons license", - "submit.progressbar.detect-duplicate": "Potential duplicates", - - "upload": { - "no-entry": "No", - "no-file-uploaded": "No file uploaded yet.", - "info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", - "drop-message": "Drop files to attach them to the item", - "upload-successful": "Upload successful", - "upload-failed": "Upload failed", - "header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", - "header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - "form": { - "access-condition-label": "Access condition type", - "from-label": "Access grant from", - "from-placeholder": "From", - "until-label": "Access grant until", - "until-placeholder": "Until", - "group-label": "Group", - "group-required": "Group is required.", - "date-required": "Date is required." - }, - "save-metadata": "Save metadata", - "undo": "Cancel", - "delete": { - "submit": "Delete", - "confirm": { - "cancel": "Cancel", - "submit": "Yes, I'm sure", - "title": "Delete bitstream", - "info": "This operation can't be undone. Are you sure?" - } - } - } - }, - "workflow": { - "generic": { - "delete": "Delete", - "delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", - "edit": "Edit", - "edit-help": "Select this option to change the item's metadata.", - "view": "View", - "view-help": "Select this option to view the item's metadata." - }, - "tasks": { - "generic": { - "processing": "Processing...", - "success": "Operation successful", - "error": "Error occurred during operation...", - "submitter": "Submitter" - }, - "claimed": { - "approve": "Approve", - "approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".", - "edit": "Edit", - "edit_help": "Select this option to change the item's metadata.", - "reject": { - "submit": "Reject", - "reason": { - "submit": "Reject item", - "title": "Reason", - "info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", - "placeholder": "Describe the reason of reject" - } - }, - "reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", - "return": "Return to pool", - "return_help": "Return the task to the pool so that another user may perform the task." - - }, - "pool": { - "claim": "Claim", - "claim_help": "Assign this task to yourself.", - "show-detail": "Show detail", - "hide-detail": "Hide detail" - } - } - } - }, - "uploader": { - "drag-message": "Drag & Drop your files here", - "or": ", or", - "browse": "browse", - "queue-lenght": "Queue length", - "processing": "Processing" - } -} + "uploader.browse": "browse", + "uploader.drag-message": "Drag & Drop your files here", + "uploader.or": ", or", + "uploader.processing": "Processing" + "uploader.queue-lenght": "Queue length" +} \ No newline at end of file From 863189b2e56be071dda527d80bbb83ab5c43eb1e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 14 Aug 2019 11:48:51 +0200 Subject: [PATCH 41/60] fix missing , in messages file --- resources/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index c5b542c424..c63e52e4b3 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -687,6 +687,6 @@ "uploader.browse": "browse", "uploader.drag-message": "Drag & Drop your files here", "uploader.or": ", or", - "uploader.processing": "Processing" + "uploader.processing": "Processing", "uploader.queue-lenght": "Queue length" } \ No newline at end of file From d1dbd891b4cc43862e6b8ed598409d0f924b426a Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 14 Aug 2019 13:05:20 +0200 Subject: [PATCH 42/60] 62355: solved lint error --- .../+search-page/configuration-search-page.component.ts | 2 +- src/app/+search-page/filtered-search-page.component.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index 85619e8f04..b1a94fc086 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -4,12 +4,12 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; -import { RouteService } from '../shared/services/route.service'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { Observable } from 'rxjs'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { map } from 'rxjs/operators'; +import { RouteService } from '../core/services/route.service'; /** * This component renders a search page using a configuration as input. diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts index 85d521ee15..0bcc9e14e3 100644 --- a/src/app/+search-page/filtered-search-page.component.ts +++ b/src/app/+search-page/filtered-search-page.component.ts @@ -4,14 +4,12 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; -import { RouteService } from '../core/services/route.service'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { Observable } from 'rxjs'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { map } from 'rxjs/operators'; -import { isEmpty, isNotEmpty } from '../shared/empty.util'; -import { ActivatedRoute } from '@angular/router'; +import { RouteService } from '../core/services/route.service'; /** * This component renders a simple item page. @@ -55,9 +53,6 @@ export class FilteredSearchPageComponent extends SearchPageComponent implements * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { - if (isEmpty(this.fixedFilter$)) { - this.fixedFilter$ = this.routeService.getRouteParameterValue('filter'); - } super.ngOnInit(); } From 737792a1df964ad1e40dc9f5678924af2c532107 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 14 Aug 2019 15:05:25 +0200 Subject: [PATCH 43/60] 62741: Moved entity mantis files to correct location --- src/app/+item-page/item-page.module.ts | 3 ++- .../item-pages}/journal-issue/journal-issue.component.html | 0 .../item-pages}/journal-issue/journal-issue.component.scss | 2 +- .../item-pages}/journal-volume/journal-volume.component.html | 0 .../item-pages}/journal-volume/journal-volume.component.scss | 2 +- .../item-pages}/journal/journal.component.html | 0 .../item-pages}/journal/journal.component.scss | 2 +- .../item-pages}/orgunit/orgunit.component.html | 0 .../item-pages}/orgunit/orgunit.component.scss | 2 +- .../research-entities/item-pages}/person/person.component.html | 0 .../research-entities/item-pages}/person/person.component.scss | 2 +- .../item-pages}/project/project.component.html | 0 .../item-pages}/project/project.component.scss | 2 +- 13 files changed, 8 insertions(+), 7 deletions(-) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal-issue/journal-issue.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal-issue/journal-issue.component.scss (85%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal-volume/journal-volume.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal-volume/journal-volume.component.scss (85%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal/journal.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/journal-entities/item-pages}/journal/journal.component.scss (89%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/orgunit/orgunit.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/orgunit/orgunit.component.scss (86%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/person/person.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/person/person.component.scss (89%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/project/project.component.html (100%) rename themes/mantis/app/{+item-page/simple/item-types => entity-groups/research-entities/item-pages}/project/project.component.scss (86%) diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 6743028b6c..f510ccf19b 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -62,7 +62,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field GenericItemPageFieldComponent, RelatedEntitiesSearchComponent, RelatedItemsComponent, - MetadataRepresentationListComponent + MetadataRepresentationListComponent, + ItemPageTitleFieldComponent ], entryComponents: [ PublicationComponent diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.html rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss similarity index 85% rename from themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss index 7ce24acc15..3caa55f533 100644 --- a/themes/mantis/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss'; +@import 'src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss'; :host { > * { diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.html rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss similarity index 85% rename from themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss index ab1bc700b1..5c2534b318 100644 --- a/themes/mantis/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss'; +@import 'src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss'; :host { > * { diff --git a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/journal/journal.component.html rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss similarity index 89% rename from themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss rename to themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss index 6d97cbf5c3..5c0d1c44b8 100644 --- a/themes/mantis/app/+item-page/simple/item-types/journal/journal.component.scss +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/journal/journal.component.scss'; +@import 'src/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss'; :host { > * { diff --git a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss similarity index 86% rename from themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss index 5b2bdb0382..54651aede0 100644 --- a/themes/mantis/app/+item-page/simple/item-types/orgunit/orgunit.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/orgunit/orgunit.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss'; :host { > * { diff --git a/themes/mantis/app/+item-page/simple/item-types/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/person/person.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/person/person.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss similarity index 89% rename from themes/mantis/app/+item-page/simple/item-types/person/person.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss index 3b454aab0e..48571b05b2 100644 --- a/themes/mantis/app/+item-page/simple/item-types/person/person.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/person/person.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/person/person.component.scss'; :host { > * { diff --git a/themes/mantis/app/+item-page/simple/item-types/project/project.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html similarity index 100% rename from themes/mantis/app/+item-page/simple/item-types/project/project.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html diff --git a/themes/mantis/app/+item-page/simple/item-types/project/project.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss similarity index 86% rename from themes/mantis/app/+item-page/simple/item-types/project/project.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss index 9c9aa9c629..d2707d30cc 100644 --- a/themes/mantis/app/+item-page/simple/item-types/project/project.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/+item-page/simple/item-types/project/project.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/project/project.component.scss'; :host { > * { From 7d0439b006c259056f359f2ac111d2baa7534c06 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 14 Aug 2019 16:00:26 +0200 Subject: [PATCH 44/60] 64225: Fixed AoT build errors --- .../edit-relationship-list.component.spec.ts | 1 - .../edit-relationship/edit-relationship.component.spec.ts | 1 - .../item-relationships/item-relationships.component.spec.ts | 1 - src/app/core/data/relationship.service.spec.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index bd5f5f2e5c..3748ebca9d 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -36,7 +36,6 @@ let relationshipType; describe('EditRelationshipListComponent', () => { beforeEach(async(() => { relationshipType = Object.assign(new RelationshipType(), { - type: ResourceType.RelationshipType, id: '1', uuid: '1', leftLabel: 'isAuthorOfPublication', diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts index fc6c999a1c..3306d8eb01 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts @@ -32,7 +32,6 @@ let el; describe('EditRelationshipComponent', () => { beforeEach(async(() => { relationshipType = Object.assign(new RelationshipType(), { - type: ResourceType.RelationshipType, id: '1', uuid: '1', leftLabel: 'isAuthorOfPublication', diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts index 51394ef9e5..b1a4e11371 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts @@ -66,7 +66,6 @@ describe('ItemRelationshipsComponent', () => { const date = new Date(); relationshipType = Object.assign(new RelationshipType(), { - type: ResourceType.RelationshipType, id: '1', uuid: '1', leftLabel: 'isAuthorOfPublication', diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts index 88da4a5496..0ced517d74 100644 --- a/src/app/core/data/relationship.service.spec.ts +++ b/src/app/core/data/relationship.service.spec.ts @@ -31,7 +31,6 @@ describe('RelationshipService', () => { }) as ObjectCacheService; const relationshipType = Object.assign(new RelationshipType(), { - type: ResourceType.RelationshipType, id: '1', uuid: '1', leftLabel: 'isAuthorOfPublication', From 1b6c0a9e42c6909e952de3babef8ebbd4373260f Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 14 Aug 2019 16:20:10 +0200 Subject: [PATCH 45/60] fixed AoT errors --- .../search-filters/search-filters.component.spec.ts | 2 +- .../items/switcher/item-type-switcher.component.spec.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filters.component.spec.ts b/src/app/+search-page/search-filters/search-filters.component.spec.ts index dc883cd290..030702d93e 100644 --- a/src/app/+search-page/search-filters/search-filters.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filters.component.spec.ts @@ -58,7 +58,7 @@ describe('SearchFiltersComponent', () => { describe('when the getSearchLink method is called', () => { beforeEach(() => { spyOn(searchService, 'getSearchLink'); - comp.getSearchLink(); + (comp as any).getSearchLink(); }); it('should call getSearchLink on the searchService', () => { diff --git a/src/app/shared/items/switcher/item-type-switcher.component.spec.ts b/src/app/shared/items/switcher/item-type-switcher.component.spec.ts index 3f5b7c7f90..76389201c5 100644 --- a/src/app/shared/items/switcher/item-type-switcher.component.spec.ts +++ b/src/app/shared/items/switcher/item-type-switcher.component.spec.ts @@ -1,16 +1,14 @@ import { ItemTypeSwitcherComponent } from './item-type-switcher.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { of as observableOf } from 'rxjs'; import { PageInfo } from '../../../core/shared/page-info.model'; import { Item } from '../../../core/shared/item.model'; import { PaginatedList } from '../../../core/data/paginated-list'; -import { RemoteData } from '../../../core/data/remote-data'; import * as decorator from '../item-type-decorator'; import { getComponentByItemType, ItemViewMode } from '../item-type-decorator'; import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model'; -import createSpy = jasmine.createSpy; import { createSuccessfulRemoteDataObject$ } from '../../testing/utils'; +import createSpy = jasmine.createSpy; const relationType = 'type'; const mockItem: Item = Object.assign(new Item(), { @@ -61,7 +59,7 @@ describe('ItemTypeSwitcherComponent', () => { describe('when calling getComponent', () => { beforeEach(() => { - comp.getComponent(); + (comp as any).getComponent(); }); it('should call getComponentByItemType with parameters type and viewMode', () => { @@ -79,7 +77,7 @@ describe('ItemTypeSwitcherComponent', () => { describe('when calling getComponent', () => { beforeEach(() => { - comp.getComponent(); + (comp as any).getComponent(); }); it('should call getComponentByItemType with parameters type, viewMode and representationType', () => { From 1d05680126630c79c7328a6fffe7d7cffc719f3b Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 16 Aug 2019 13:00:04 +0200 Subject: [PATCH 46/60] Implement feedback Change padding into margin Change 'Create link' to a button Add placeholder to extension field Fix pagination --- resources/i18n/en.json | 1 + .../add-bitstream-format.component.html | 4 ++-- .../bitstream-formats/bitstream-formats.component.html | 4 ++-- .../bitstream-formats/bitstream-formats.component.ts | 10 ++++++---- .../edit-bitstream-format.component.html | 4 ++-- .../format-form/format-form.component.ts | 5 +++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index c63e52e4b3..9a4e49d433 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -17,6 +17,7 @@ "admin.registries.bitstream-formats.edit.description.label": "Description", "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.", "admin.registries.bitstream-formats.edit.extensions.label": "File extensions", + "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot", "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.", "admin.registries.bitstream-formats.edit.failure.head": "Failure", "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}", diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html index c05f3badeb..2b65b369b2 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html +++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html @@ -1,8 +1,8 @@
-
+

{{ 'admin.registries.bitstream-formats.create.new' | translate }}

+ class="border-bottom mb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }} diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html index ec1993c0f4..7dd325338a 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html @@ -2,10 +2,10 @@
- +

{{'admin.registries.bitstream-formats.description' | translate}}

-

{{'admin.registries.bitstream-formats.create.new' | translate}}

+

{{'admin.registries.bitstream-formats.create.new' | translate}}

-
+

{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}

+ class="border-bottom mb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }} diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts index febf56bc9f..505ccccd91 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts @@ -4,7 +4,7 @@ import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-f import { DynamicCheckboxModel, DynamicFormArrayModel, - DynamicFormControlLayout, + DynamicFormControlLayout, DynamicFormControlLayoutConfig, DynamicFormControlModel, DynamicFormService, DynamicInputModel, @@ -49,7 +49,7 @@ export class FormatFormComponent implements OnInit { arrayElementLayout: DynamicFormControlLayout = { grid: { group: 'form-row', - } + }, }; /** @@ -114,6 +114,7 @@ export class FormatFormComponent implements OnInit { groupFactory: () => [ new DynamicInputModel({ id: 'extension', + placeholder: 'admin.registries.bitstream-formats.edit.extensions.placeholder', }, this.arrayInputElementLayout) ] }, this.arrayElementLayout), From 88c8fddbceea980dcb690e1d4158b7db9e0bd02e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 19 Aug 2019 17:25:41 +0200 Subject: [PATCH 47/60] Fix to message, margin and deletion page reset --- resources/i18n/en.json | 4 ++-- .../bitstream-formats/bitstream-formats.component.html | 2 +- .../bitstream-formats/bitstream-formats.component.ts | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 9a4e49d433..f3236251cf 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -8,9 +8,9 @@ "admin.registries.bitstream-formats.create.new": "Add a new bitstream format", "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.", "admin.registries.bitstream-formats.create.success.head": "Success", - "admin.registries.bitstream-formats.delete.failure.amount": "The following amount of bitstream formats could not be deleted: {{ amount }} ", + "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)", "admin.registries.bitstream-formats.delete.failure.head": "Failure", - "admin.registries.bitstream-formats.delete.success.amount": "The following amount of bitstream formats have been successfully deleted: {{ amount }}", + "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)", "admin.registries.bitstream-formats.delete.success.head": "Success", "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.", "admin.registries.bitstream-formats.edit.description.hint": "", diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html index 7dd325338a..e5cf7cf5ec 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html @@ -2,7 +2,7 @@
- +

{{'admin.registries.bitstream-formats.description' | translate}}

{{'admin.registries.bitstream-formats.create.new' | translate}}

diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 737fb59c77..cb7aa1ef91 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -78,8 +78,11 @@ export class BitstreamFormatsComponent implements OnInit { } this.deselectAll(); - this.pageState.next('update-on-delete'); - }); + + this.router.navigate([], { + queryParams: Object.assign({}, { page: 1 }), + queryParamsHandling: 'merge' + }); }); } ); } From f49fab35997e9687541a944453e108d6a350528e Mon Sep 17 00:00:00 2001 From: lhenze Date: Mon, 26 Aug 2019 08:56:13 -0400 Subject: [PATCH 48/60] Added "empty" message Modeled on browse-by.component.html --- src/app/+collection-page/collection-page.component.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 91239de17c..ddfa17e580 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -52,6 +52,9 @@ message="{{'error.recent-submissions' | translate}}"> +
Date: Tue, 27 Aug 2019 13:06:13 -0700 Subject: [PATCH 49/60] add docker startup --- .travis.yml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.travis.yml b/.travis.yml index 403a10b770..66d9c26185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,44 @@ sudo: required dist: trusty + +env: + COMPOSE_VERSION: 1.24.1 + +before_install: + # Docker Compose Install + - curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose + - chmod +x docker-compose + - sudo mv docker-compose /usr/local/bin + # Download and install Docker libs + - curl -L https://github.com/Ortus-Solutions/docker-buildfiles/archive/master.zip > docker.zip + - unzip docker.zip -d workbench + - mv workbench/docker-buildfiles-master workbench/docker + # CommandBox Keys + - sudo apt-key adv --keyserver keys.gnupg.net --recv 6DA70622 + - sudo echo "deb http://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a + /etc/apt/sources.list.d/commandbox.list + - curl -L https://github.com/DSpace-Labs/DSpace-Docker-Images/archive/master.zip > master.zip + - unzip master.zip + +install: + # Core testing install + - sudo apt-get update && sudo apt-get --assume-yes install commandbox + - box install + - box server start + # Docker CFML Server Tests + - export ANGULAR_SRC=$(pwd) + - cd DSpace-Docker-Images/docker-compose-files/dspace-compose + - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml build + - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml -f load.entities.yml up -d + +before_script: + # Startup the app + - curl http://localhost:8080/ + - curl http://localhost:3000/ + +after_script: + - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml down + addons: apt: sources: From fa86601f553824bbe2ac5534eaa4274a26fee89e Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 29 Aug 2019 13:36:24 +0200 Subject: [PATCH 50/60] applied feedback --- .../search-filter/search-filter.service.ts | 2 +- .../search-fixed-filter.service.spec.ts | 15 ++++++------ .../search-label.component.spec.ts | 12 +++++----- .../search-labels.component.spec.ts | 24 +++++++++---------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 8482838101..d4713bbe2f 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -19,7 +19,7 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SearchFixedFilterService } from './search-fixed-filter.service'; import { Params } from '@angular/router'; -// const spy = create(); + const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; export const FILTER_CONFIG: InjectionToken = new InjectionToken('filterConfig'); diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts index 37a386d51a..49f97a4263 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts @@ -1,9 +1,9 @@ -import {SearchFixedFilterService} from './search-fixed-filter.service'; -import {RequestService} from '../../../core/data/request.service'; -import {HALEndpointService} from '../../../core/shared/hal-endpoint.service'; -import {of as observableOf} from 'rxjs'; -import {RequestEntry} from '../../../core/data/request.reducer'; -import {FilteredDiscoveryQueryResponse} from '../../../core/cache/response.models'; +import { SearchFixedFilterService } from './search-fixed-filter.service'; +import { RequestService } from '../../../core/data/request.service'; +import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; +import { of as observableOf } from 'rxjs'; +import { RequestEntry } from '../../../core/data/request.reducer'; +import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models'; describe('SearchFixedFilterService', () => { let service: SearchFixedFilterService; @@ -12,7 +12,8 @@ describe('SearchFixedFilterService', () => { const requestServiceStub = Object.assign({ /* tslint:disable:no-empty */ - configure: () => {}, + configure: () => { + }, /* tslint:enable:no-empty */ generateRequestId: () => 'fake-id', getByHref: () => observableOf(Object.assign(new RequestEntry(), { diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts index 329c286ce3..a2603b7b8b 100644 --- a/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts @@ -5,12 +5,12 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Observable, of as observableOf } from 'rxjs'; import { Params } from '@angular/router'; -import {SearchLabelComponent} from './search-label.component'; -import {ObjectKeysPipe} from '../../../shared/utils/object-keys-pipe'; -import {SearchService} from '../../search-service/search.service'; -import {SEARCH_CONFIG_SERVICE} from '../../../+my-dspace-page/my-dspace-page.component'; -import {SearchServiceStub} from '../../../shared/testing/search-service-stub'; -import {SearchConfigurationServiceStub} from '../../../shared/testing/search-configuration-service-stub'; +import { SearchLabelComponent } from './search-label.component'; +import { ObjectKeysPipe } from '../../../shared/utils/object-keys-pipe'; +import { SearchService } from '../../search-service/search.service'; +import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; +import { SearchServiceStub } from '../../../shared/testing/search-service-stub'; +import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service-stub'; describe('SearchLabelComponent', () => { let comp: SearchLabelComponent; diff --git a/src/app/+search-page/search-labels/search-labels.component.spec.ts b/src/app/+search-page/search-labels/search-labels.component.spec.ts index 5a554e42d6..7984805206 100644 --- a/src/app/+search-page/search-labels/search-labels.component.spec.ts +++ b/src/app/+search-page/search-labels/search-labels.component.spec.ts @@ -1,14 +1,14 @@ -import {SearchLabelsComponent} from './search-labels.component'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {TranslateModule} from '@ngx-translate/core'; -import {SearchService} from '../search-service/search.service'; -import {ChangeDetectionStrategy, NO_ERRORS_SCHEMA} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {SearchServiceStub} from '../../shared/testing/search-service-stub'; -import {of as observableOf} from 'rxjs'; -import {ObjectKeysPipe} from '../../shared/utils/object-keys-pipe'; -import {SEARCH_CONFIG_SERVICE} from '../../+my-dspace-page/my-dspace-page.component'; +import { SearchLabelsComponent } from './search-labels.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { SearchService } from '../search-service/search.service'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { SearchServiceStub } from '../../shared/testing/search-service-stub'; +import { of as observableOf } from 'rxjs'; +import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe'; +import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; describe('SearchLabelsComponent', () => { let comp: SearchLabelsComponent; @@ -34,7 +34,7 @@ describe('SearchLabelsComponent', () => { declarations: [SearchLabelsComponent, ObjectKeysPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, - { provide: SEARCH_CONFIG_SERVICE, useValue: {getCurrentFrontendFilters : () => observableOf(mockFilters)} } + { provide: SEARCH_CONFIG_SERVICE, useValue: { getCurrentFrontendFilters: () => observableOf(mockFilters) } } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(SearchLabelsComponent, { From c8fafd067e4939657e8cd9b9fd29ae24be62b7dc Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 27 Aug 2019 13:12:31 -0700 Subject: [PATCH 51/60] run e2e tests in docker using docker-compose --- .travis.yml | 37 ++++++++++++------------------------- package.json | 6 +++--- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66d9c26185..c161e85977 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,41 +3,31 @@ dist: trusty env: COMPOSE_VERSION: 1.24.1 + DSPACE_REST_HOST: localhost + DSPACE_REST_PORT: 8080 + DSPACE_REST_NAMESPACE: '/server/api' + DSPACE_REST_SSL: false before_install: # Docker Compose Install - curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin - # Download and install Docker libs - - curl -L https://github.com/Ortus-Solutions/docker-buildfiles/archive/master.zip > docker.zip - - unzip docker.zip -d workbench - - mv workbench/docker-buildfiles-master workbench/docker - # CommandBox Keys - - sudo apt-key adv --keyserver keys.gnupg.net --recv 6DA70622 - - sudo echo "deb http://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a - /etc/apt/sources.list.d/commandbox.list - - curl -L https://github.com/DSpace-Labs/DSpace-Docker-Images/archive/master.zip > master.zip - - unzip master.zip + - git clone https://github.com/DSpace-Labs/DSpace-Docker-Images.git install: - # Core testing install - - sudo apt-get update && sudo apt-get --assume-yes install commandbox - - box install - - box server start - # Docker CFML Server Tests - - export ANGULAR_SRC=$(pwd) - - cd DSpace-Docker-Images/docker-compose-files/dspace-compose - - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml build - - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml -f load.entities.yml up -d + - docker-compose version + - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml up -d + - travis_retry yarn install + # allow starup time + #- sleep 60 before_script: # Startup the app - - curl http://localhost:8080/ - - curl http://localhost:3000/ + #- curl http://localhost:8080/ after_script: - - docker-compose -f docker-compose.yml -f d7.override.yml -f agn-src-override.yml down + - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml down addons: apt: @@ -57,9 +47,6 @@ cache: bundler_args: --retry 5 -install: - - travis_retry yarn install - script: # Use Chromium instead of Chrome. - export CHROME_BIN=chromium-browser diff --git a/package.json b/package.json index 7916379039..cb4ced69d7 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld", "clean": "yarn run clean:prod && yarn run clean:node", "prebuild": "yarn run clean:bld && yarn run clean:dist", - "prebuild:aot": "yarn run prebuild", + "prebuild:ci": "yarn run prebuild", "prebuild:prod": "yarn run prebuild", "build": "node ./scripts/webpack.js --progress --mode development", - "build:aot": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development", + "build:ci": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development", "build:prod": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode production && node ./scripts/webpack.js --env.aot --env.client --mode production", "postbuild:prod": "yarn run rollup", "rollup": "rollup -c rollup.config.js", @@ -51,7 +51,7 @@ "debug:server": "node-nightly --inspect --debug-brk dist/server.js", "debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --mode development", "debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production", - "ci": "yarn run lint && yarn run build:aot && yarn run test:headless", + "ci": "yarn run lint && yarn run build:ci && yarn run test:headless && npm-run-all -p -r server e2e", "protractor": "node node_modules/protractor/bin/protractor", "pree2e": "yarn run webdriver:update", "e2e": "yarn run protractor", From b68c59b83506a9a2a8c1b4371a7bdba27362da81 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Sun, 1 Sep 2019 16:25:26 -0700 Subject: [PATCH 52/60] Updating comments per review --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c161e85977..cff5a11c2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,11 @@ sudo: required dist: trusty env: + # Install the latest docker-compose version for ci testing. + # The default installation in travis is not compatible with the latest docker-compose file version. COMPOSE_VERSION: 1.24.1 + # The ci step will test the dpsace-angular code against DSpace REST. + # Direct that step to utilize a DSpace REST service that has been started in docker. DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: '/server/api' @@ -19,11 +23,10 @@ install: - docker-compose version - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml up -d - travis_retry yarn install - # allow starup time - #- sleep 60 before_script: - # Startup the app + # The following line could be enabled to verify that the rest server is repsonding. + # Currently, "yarn run build" takes enough time to run to allow the service to be available #- curl http://localhost:8080/ after_script: From 64db1c242e965cafe0eb8a8bb8feaf0a4ac71d17 Mon Sep 17 00:00:00 2001 From: lhenze Date: Tue, 3 Sep 2019 17:02:09 -0400 Subject: [PATCH 53/60] New key created for *no recent items in this collection* --- resources/i18n/en.json | 1 + src/app/+collection-page/collection-page.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index f3236251cf..234967231e 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -132,6 +132,7 @@ "collection.form.tableofcontents": "News (HTML)", "collection.form.title": "Name", "collection.page.browse.recent.head": "Recent Submissions", + "collection.page.browse.recent.empty": "No items to show", "collection.page.license": "License", "collection.page.news": "News", "community.create.head": "Create a Community", diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index ddfa17e580..2b16bc1ca6 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -53,7 +53,7 @@
From 9723d929e12ba0d9f1c52208053cd84848346435 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 5 Sep 2019 10:44:37 +0200 Subject: [PATCH 54/60] resolved issues after merge with master --- .../journal-issue/journal-issue-grid-element.component.html | 2 +- .../journal-volume/journal-volume-grid-element.component.html | 2 +- .../journal/journal-grid-element.component.html | 2 +- .../orgunit/orgunit-grid-element.component.html | 2 +- .../person/person-grid-element.component.html | 2 +- .../project/project-grid-element.component.html | 2 +- .../publication/publication-grid-element.component.html | 2 +- .../search-result-grid-element.component.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html index 4cb34a140b..3aa79fc70a 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html index d7c9b68a24..b2b251f550 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html index 467cdd1594..af0739004c 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index 104d3a0a57..a4765c4e8f 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index 86353377fa..331c2bd520 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index a595791cc4..889276b29b 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html index e2477524ca..e735dbf107 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts index af1768b0e5..63b2536043 100644 --- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts @@ -22,7 +22,7 @@ export class SearchResultGridElementComponent, K exten super(listableObject); if (hasValue(this.object)) { this.dso = this.object.indexableObject; - this.isCollapsed$ = this.isCollapsed(); + this.isCollapsed$ = this.isCollapsed(); } } From 6c4d461ade3b970bd8532855ef4941fa40c5dd64 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 5 Sep 2019 18:24:03 +0200 Subject: [PATCH 55/60] master travis test fixes --- config/environment.test.js | 4 +++- .../journal-issue-grid-element.component.spec.ts | 7 +++++-- .../journal-volume-grid-element.component.spec.ts | 7 +++++-- .../journal/journal-grid-element.component.spec.ts | 7 +++++-- .../orgunit/orgunit-grid-element.component.spec.ts | 7 +++++-- .../person/person-grid-element.component.spec.ts | 7 +++++-- .../project/project-grid-element.component.spec.ts | 7 +++++-- .../publication/publication-grid-element.component.spec.ts | 7 +++++-- 8 files changed, 38 insertions(+), 15 deletions(-) diff --git a/config/environment.test.js b/config/environment.test.js index f4d625303f..0652755bc7 100644 --- a/config/environment.test.js +++ b/config/environment.test.js @@ -1,3 +1,5 @@ module.exports = { - + theme: { + name: 'default', + } }; diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts index 68a05b66c3..d13feda406 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts index 5a8fca5fc6..8c854aeb77 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts index 8c12c1a266..0d0f77842a 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { JournalGridElementComponent } from './journal-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts index 39ddea4c7b..15c7b75bf5 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { OrgunitGridElementComponent } from './orgunit-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts index a0f8e4c29e..25268261e1 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { PersonGridElementComponent } from './person-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts index 9ad26935b7..969912976c 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts @@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec'; import { ProjectGridElementComponent } from './project-grid-element.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -27,7 +30,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts index f067a21ae0..e0ba26fcfc 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts @@ -9,11 +9,14 @@ import { of as observableOf } from 'rxjs/internal/observable/of'; import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model'; import { Item } from '../../../../../core/shared/item.model'; import { ITEM } from '../../../../items/switcher/item-type-switcher.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../testing/utils'; +import { PaginatedList } from '../../../../../core/data/paginated-list'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { @@ -45,7 +48,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), { - bitstreams: observableOf({}), + bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { From 92f8a5d5224a5a6a5a17ce819f70d7683b1996c2 Mon Sep 17 00:00:00 2001 From: Julius Gruber Date: Fri, 6 Sep 2019 13:22:43 +0200 Subject: [PATCH 56/60] Typo in dropdown-field-parser.ts fixed --- src/app/shared/form/builder/parsers/dropdown-field-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts index 279f78f721..1623829b15 100644 --- a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts +++ b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts @@ -29,7 +29,7 @@ export class DropdownFieldParser extends FieldParser { const dropdownModel = new DynamicScrollableDropdownModel(dropdownModelConfig, layout); return dropdownModel; } else { - throw Error(`Authority name is not available. Please checks form configuration file.`); + throw Error(`Authority name is not available. Please check the form configuration file.`); } } } From 431d7e12b112f1b3b4ec96026a4017f12152523d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 6 Sep 2019 14:09:21 +0200 Subject: [PATCH 57/60] 62589: Clean build folder pre-tests --- config/environment.test.js | 4 +--- package.json | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config/environment.test.js b/config/environment.test.js index 0652755bc7..f4d625303f 100644 --- a/config/environment.test.js +++ b/config/environment.test.js @@ -1,5 +1,3 @@ module.exports = { - theme: { - name: 'default', - } + }; diff --git a/package.json b/package.json index 7916379039..3f5bda522b 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,9 @@ "protractor": "node node_modules/protractor/bin/protractor", "pree2e": "yarn run webdriver:update", "e2e": "yarn run protractor", + "pretest": "yarn run clean:bld", + "pretest:headless": "yarn run pretest", + "pretest:watch": "yarn run pretest", "test": "karma start --single-run", "test:headless": "karma start --single-run --browsers ChromeHeadless", "test:watch": "karma start --no-single-run --auto-watch", From bf039978beac6b2ba3d17bcdcf0a918b8b94cec8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 6 Sep 2019 14:30:57 +0200 Subject: [PATCH 58/60] 62589: test environment comment --- config/environment.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environment.test.js b/config/environment.test.js index f4d625303f..6897e29aa7 100644 --- a/config/environment.test.js +++ b/config/environment.test.js @@ -1,3 +1,4 @@ +// This configuration is currently only being used for unit tests, end-to-end tests use environment.dev.ts module.exports = { }; From ddd1bb42cf4580913e7db4847cf5e62257dc0629 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Sep 2019 08:38:48 -0500 Subject: [PATCH 59/60] Spelling fixes --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cff5a11c2c..ee3604ffc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ env: # Install the latest docker-compose version for ci testing. # The default installation in travis is not compatible with the latest docker-compose file version. COMPOSE_VERSION: 1.24.1 - # The ci step will test the dpsace-angular code against DSpace REST. + # The ci step will test the dspace-angular code against DSpace REST. # Direct that step to utilize a DSpace REST service that has been started in docker. DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 @@ -25,7 +25,7 @@ install: - travis_retry yarn install before_script: - # The following line could be enabled to verify that the rest server is repsonding. + # The following line could be enabled to verify that the rest server is responding. # Currently, "yarn run build" takes enough time to run to allow the service to be available #- curl http://localhost:8080/ From e51838002518067b4b04cff129ed5dc2a27e144a Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 6 Sep 2019 16:07:37 +0200 Subject: [PATCH 60/60] fixed import issues --- src/app/app.component.spec.ts | 2 +- src/app/app.component.ts | 3 +-- src/app/app.module.ts | 2 +- src/app/shared/lang-switch/lang-switch.component.spec.ts | 2 +- src/app/shared/lang-switch/lang-switch.component.ts | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index d6315ce4f6..5b78e3462f 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -44,8 +44,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouteService } from './core/services/route.service'; import { MockActivatedRoute } from './shared/mocks/mock-active-router'; import { MockRouter } from './shared/mocks/mock-router'; -import { CookieService } from './shared/services/cookie.service'; import { MockCookieService } from './shared/mocks/mock-cookie.service'; +import { CookieService } from './core/services/cookie.service'; let comp: AppComponent; let fixture: ComponentFixture; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c6fd538fa..50baaf6e57 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,9 +32,8 @@ import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs'; import { slideSidebarPadding } from './shared/animations/slide'; import { HostWindowService } from './shared/host-window.service'; import { Theme } from '../config/theme.inferface'; -import { ClientCookieService } from './shared/services/client-cookie.service'; import { isNotEmpty } from './shared/empty.util'; -import { CookieService } from './shared/services/cookie.service'; +import { CookieService } from './core/services/cookie.service'; export const LANG_COOKIE = 'language_cookie'; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3781edf532..916788df8c 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,7 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e import { NavbarModule } from './navbar/navbar.module'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; -import { ClientCookieService } from './shared/services/client-cookie.service'; +import { ClientCookieService } from './core/services/client-cookie.service'; export function getConfig() { return ENV_CONFIG; diff --git a/src/app/shared/lang-switch/lang-switch.component.spec.ts b/src/app/shared/lang-switch/lang-switch.component.spec.ts index 0f84901fbc..5b10578f77 100644 --- a/src/app/shared/lang-switch/lang-switch.component.spec.ts +++ b/src/app/shared/lang-switch/lang-switch.component.spec.ts @@ -7,8 +7,8 @@ import { GLOBAL_CONFIG } from '../../../config'; import {LangConfig} from '../../../config/lang-config.interface'; import {Observable, of} from 'rxjs'; import { By } from '@angular/platform-browser'; -import { CookieService } from '../services/cookie.service'; import { MockCookieService } from '../mocks/mock-cookie.service'; +import { CookieService } from '../../core/services/cookie.service'; // This test is completely independent from any message catalogs or keys in the codebase // The translation module is instantiated with these bogus messages that we aren't using anyway. diff --git a/src/app/shared/lang-switch/lang-switch.component.ts b/src/app/shared/lang-switch/lang-switch.component.ts index 3b490b98d4..e91ed5c9a2 100644 --- a/src/app/shared/lang-switch/lang-switch.component.ts +++ b/src/app/shared/lang-switch/lang-switch.component.ts @@ -2,9 +2,8 @@ import {Component, Inject, OnInit} from '@angular/core'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import {TranslateService} from '@ngx-translate/core'; import {LangConfig} from '../../../config/lang-config.interface'; -import { ClientCookieService } from '../services/client-cookie.service'; import { LANG_COOKIE } from '../../app.component'; -import { CookieService } from '../services/cookie.service'; +import { CookieService } from '../../core/services/cookie.service'; @Component({ selector: 'ds-lang-switch',