From aedd4b99eec216cce6fc63978fd1d1b6db0b6e40 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 14 Nov 2019 13:29:39 +0100 Subject: [PATCH 01/12] 66155: Show more/less incremental on item pages + loading component for entities --- resources/i18n/en.json5 | 4 +- ...etadata-representation-list.component.html | 27 +++++--- .../metadata-representation-list.component.ts | 47 ++++++++++++-- .../related-items/related-items-component.ts | 64 +++++++++++++------ .../related-items.component.html | 25 +++++--- 5 files changed, 119 insertions(+), 48 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 41d868dded..3923826dae 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -405,8 +405,8 @@ "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.related-items.view-more": "View more", - "item.page.related-items.view-less": "View less", + "item.page.related-items.view-more": "Show {{ amount }} more", + "item.page.related-items.view-less": "Hide last {{ amount }}", "item.page.subject": "Keywords", "item.page.uri": "URI", diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html index e9d2364ca6..07f3d88eba 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html @@ -1,11 +1,18 @@ - - - - - + + + + + + + diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts index 521a93523d..3e95c45f01 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model'; import { ItemViewMode } from '../../../shared/items/item-type-decorator'; import { Observable } from 'rxjs/internal/Observable'; @@ -12,6 +12,8 @@ import { filter, map, switchMap } from 'rxjs/operators'; import { getSucceededRemoteData } from '../../../core/shared/operators'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { PaginatedList } from '../../../core/data/paginated-list'; @Component({ selector: 'ds-metadata-representation-list', @@ -23,7 +25,7 @@ import { ItemMetadataRepresentation } from '../../../core/shared/metadata-repres * It expects an itemType to resolve the metadata to a an item * It expects a label to put on top of the list */ -export class MetadataRepresentationListComponent implements OnInit { +export class MetadataRepresentationListComponent implements OnInit, OnDestroy { /** * The parent of the list of related items to display */ @@ -51,6 +53,18 @@ export class MetadataRepresentationListComponent implements OnInit { */ @Input() limit = 10; + /** + * The amount to increment the list by when clicking "view more" + * Defaults to 10 + * The default can optionally be overridden by providing the limit as input to the component + */ + @Input() incrementBy = 10; + + /** + * Is the list (re-)loading? + */ + loading = false; + /** * A list of metadata-representations to display */ @@ -73,6 +87,11 @@ export class MetadataRepresentationListComponent implements OnInit { */ total: number; + /** + * Subscription on representations used to update the "loading" property of this component + */ + representationsSub: Subscription; + constructor(public relationshipService: RelationshipService) { } @@ -88,6 +107,9 @@ export class MetadataRepresentationListComponent implements OnInit { const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField); this.total = metadata.length; this.representations$ = this.resolveMetadataRepresentations(metadata); + this.representationsSub = this.representations$.subscribe((represenations: MetadataRepresentation[]) => { + this.loading = represenations.length !== this.limit && represenations.length !== this.total; + }); } /** @@ -124,18 +146,31 @@ export class MetadataRepresentationListComponent implements OnInit { } /** - * Expand the list to display all metadata representations + * Expand the list to display more metadata representations */ viewMore() { - this.limit = 9999; + this.limit = this.limit + this.incrementBy; + this.loading = true; this.setRepresentations(); } /** - * Collapse the list to display the originally displayed metadata representations + * Collapse the list to display less metadata representations */ viewLess() { - this.limit = this.originalLimit; + if (this.limit > this.originalLimit) { + this.limit = this.limit - this.incrementBy; + } + this.loading = true; this.setRepresentations(); } + + /** + * Unsubscribe from the representations subscription + */ + ngOnDestroy(): void { + if (this.representationsSub) { + this.representationsSub.unsubscribe(); + } + } } diff --git a/src/app/+item-page/simple/related-items/related-items-component.ts b/src/app/+item-page/simple/related-items/related-items-component.ts index 5b4376bab0..074c2f6716 100644 --- a/src/app/+item-page/simple/related-items/related-items-component.ts +++ b/src/app/+item-page/simple/related-items/related-items-component.ts @@ -32,10 +32,24 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { @Input() relationType: string; /** - * Default options to start a search request with - * Optional input, should you wish a different page size (or other options) + * The max amount of relations to display + * Defaults to 5 + * The default can optionally be overridden by providing the limit as input to the component */ - @Input() options = Object.assign(new FindAllOptions(), { elementsPerPage: 5 }); + @Input() limit = 5; + + /** + * The amount to increment the list by when clicking "view more" + * Defaults to 5 + * The default can optionally be overridden by providing the limit as input to the component + */ + @Input() incrementBy = 5; + + /** + * Default options to start a search request with + * Optional input + */ + @Input() options = new FindAllOptions(); /** * An i18n label to use as a title for the list (usually describes the relation) @@ -43,20 +57,15 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { @Input() label: string; /** - * Completely hide the component until there's at least one item visible + * Is the list (re-)loading? */ - @HostBinding('class.d-none') hidden = true; + loading = false; /** * The list of related items */ items$: Observable>>; - /** - * Search options for displaying all elements in a list - */ - allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 }); - /** * The view-mode we're currently on * @type {ElementViewMode} @@ -64,12 +73,13 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { viewMode = ItemViewMode.Element; /** - * Whether or not the list is currently expanded to show all related items + * The originally provided limit + * Used for comparing the current size with the original */ - showingAll = false; + originalLimit: number; /** - * Subscription on items used to update the "hidden" property of this component + * Subscription on items used to update the "loading" property of this component */ itemSub: Subscription; @@ -77,26 +87,38 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options); + this.originalLimit = this.limit; + this.reloadItems(); + } + + /** + * Reload the current list of items (using the current limit) + */ + reloadItems() { + this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.limit })); this.itemSub = this.items$.subscribe((itemsRD: RemoteData>) => { - this.hidden = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0); + this.loading = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0); }); } /** - * Expand the list to display all related items + * Expand the list to display more */ viewMore() { - this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.allOptions); - this.showingAll = true; + this.limit = this.limit + this.incrementBy; + this.loading = true; + this.reloadItems(); } /** - * Collapse the list to display the originally displayed items + * Collapse the list to display less */ viewLess() { - this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options); - this.showingAll = false; + if (this.limit > this.originalLimit) { + this.limit = this.limit - this.incrementBy; + } + this.loading = true; + this.reloadItems(); } /** diff --git a/src/app/+item-page/simple/related-items/related-items.component.html b/src/app/+item-page/simple/related-items/related-items.component.html index a4baee9825..97be4150ad 100644 --- a/src/app/+item-page/simple/related-items/related-items.component.html +++ b/src/app/+item-page/simple/related-items/related-items.component.html @@ -1,11 +1,18 @@ - - + + - - - + + + + From 4bddbb6c62bd3b8ba7a8d4b940e51418353cde63 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 14 Nov 2019 14:02:40 +0100 Subject: [PATCH 02/12] 66155: Test fixes --- ...data-representation-list.component.spec.ts | 15 ++++++++----- .../related-items.component.spec.ts | 21 +++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts index 120e846523..5b5ccd31ef 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts @@ -7,6 +7,7 @@ import { Item } from '../../../core/shared/item.model'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils'; import { TranslateModule } from '@ngx-translate/core'; +import { VarDirective } from '../../../shared/utils/var.directive'; const itemType = 'Person'; const metadataField = 'dc.contributor.author'; @@ -64,7 +65,7 @@ describe('MetadataRepresentationListComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [MetadataRepresentationListComponent], + declarations: [MetadataRepresentationListComponent, VarDirective], providers: [ { provide: RelationshipService, useValue: relationshipService } ], @@ -93,12 +94,16 @@ describe('MetadataRepresentationListComponent', () => { }); describe('when viewMore is called', () => { + let originalLimit; + beforeEach(() => { + // Store the original value of limit + originalLimit = comp.limit; comp.viewMore(); }); - it('should set the limit to a high number in order to retrieve all metadata representations', () => { - expect(comp.limit).toBeGreaterThanOrEqual(999); + it('should increment the limit with incrementBy', () => { + expect(comp.limit).toEqual(originalLimit + comp.incrementBy); }); }); @@ -108,8 +113,8 @@ describe('MetadataRepresentationListComponent', () => { beforeEach(() => { // Store the original value of limit originalLimit = comp.limit; - // Set limit to a random number - comp.limit = 458; + // Increase limit with incrementBy + comp.limit = originalLimit + comp.incrementBy; comp.viewLess(); }); diff --git a/src/app/+item-page/simple/related-items/related-items.component.spec.ts b/src/app/+item-page/simple/related-items/related-items.component.spec.ts index 6637091f02..842ac32bfb 100644 --- a/src/app/+item-page/simple/related-items/related-items.component.spec.ts +++ b/src/app/+item-page/simple/related-items/related-items.component.spec.ts @@ -9,6 +9,7 @@ import { createRelationshipsObservable } from '../item-types/shared/item.compone import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils'; import { RelationshipService } from '../../../core/data/relationship.service'; import { TranslateModule } from '@ngx-translate/core'; +import { VarDirective } from '../../../shared/utils/var.directive'; const parentItem: Item = Object.assign(new Item(), { bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), @@ -42,7 +43,7 @@ describe('RelatedItemsComponent', () => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [RelatedItemsComponent], + declarations: [RelatedItemsComponent, VarDirective], providers: [ { provide: RelationshipService, useValue: relationshipService } ], @@ -71,25 +72,23 @@ describe('RelatedItemsComponent', () => { }); it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => { - expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.allOptions); - }); - - it('should set showingAll to true', () => { - expect(comp.showingAll).toEqual(true); + expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.limit + comp.incrementBy })); }); }); describe('when viewLess is called', () => { + let originalLimit; + beforeEach(() => { + // Store the original value of limit + originalLimit = comp.limit; + // Increase limit with incrementBy + comp.limit = originalLimit + comp.incrementBy; comp.viewLess(); }); it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => { - expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.options); - }); - - it('should set showingAll to false', () => { - expect(comp.showingAll).toEqual(false); + expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: originalLimit })); }); }); From 380e02a92d6b3693337d375bd3991ff321d9fe56 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 29 Nov 2019 10:17:11 +0100 Subject: [PATCH 03/12] 66155: Fixed 'hide last x' algorithm --- .../metadata-representation-list.component.html | 2 +- .../simple/related-items/related-items.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html index 97ee7ba4c1..e424e2c907 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html @@ -11,7 +11,7 @@
{{'item.page.related-items.view-less' | - translate:{ amount: (representations?.length < limit) ? limit - representations?.length : incrementBy } }} + translate:{ amount: (representations?.length < limit) ? (representations?.length + incrementBy) - limit : incrementBy } }}
diff --git a/src/app/+item-page/simple/related-items/related-items.component.html b/src/app/+item-page/simple/related-items/related-items.component.html index af16af3f6c..d6251e2302 100644 --- a/src/app/+item-page/simple/related-items/related-items.component.html +++ b/src/app/+item-page/simple/related-items/related-items.component.html @@ -11,7 +11,7 @@
{{'item.page.related-items.view-less' | - translate:{ amount: (itemsRD?.payload?.page?.length < limit) ? limit - itemsRD?.payload?.page?.length : incrementBy } }} + translate:{ amount: (itemsRD?.payload?.page?.length < limit) ? (itemsRD?.payload?.page?.length + incrementBy) - limit : incrementBy } }}
From 9e5508f9e50943cb546000d4ff8cd7a6f14bd00e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 9 Dec 2019 14:48:55 +0100 Subject: [PATCH 04/12] 66155: Refactoring metadata-representation and related-item lists with abstract incremental list and loading by pages --- .../abstract-incremental-list.component.ts | 73 ++++++++++++++++ ...etadata-representation-list.component.html | 30 +++---- .../metadata-representation-list.component.ts | 84 +++---------------- .../related-items/related-items-component.ts | 81 ++---------------- .../related-items.component.html | 30 +++---- 5 files changed, 125 insertions(+), 173 deletions(-) create mode 100644 src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts diff --git a/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts b/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts new file mode 100644 index 0000000000..e2c0823bf8 --- /dev/null +++ b/src/app/+item-page/simple/abstract-incremental-list/abstract-incremental-list.component.ts @@ -0,0 +1,73 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; + +@Component({ + selector: 'ds-abstract-incremental-list', + template: ``, +}) +/** + * An abstract component for displaying an incremental list of objects + */ +export class AbstractIncrementalListComponent implements OnInit, OnDestroy { + /** + * The amount to increment the list by + * Define this amount in the child component overriding this component + */ + incrementBy: number; + + /** + * All pages of objects to display as an array + */ + objects: T[]; + + /** + * A list of open subscriptions + */ + subscriptions: Subscription[]; + + ngOnInit(): void { + this.objects = []; + this.subscriptions = []; + this.increase(); + } + + /** + * Get a specific page + * > Override this method to return a specific page + * @param page The page to fetch + */ + getPage(page: number): T { + return undefined; + } + + /** + * Increase the amount displayed + */ + increase() { + const page = this.getPage(this.objects.length + 1); + if (hasValue(page)) { + this.objects.push(page); + } + } + + /** + * Decrease the amount displayed + */ + decrease() { + if (this.objects.length > 1) { + this.objects.pop(); + } + } + + /** + * Unsubscribe from any open subscriptions + */ + ngOnDestroy(): void { + if (isNotEmpty(this.subscriptions)) { + this.subscriptions.forEach((sub: Subscription) => { + sub.unsubscribe(); + }); + } + } +} diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html index e424e2c907..d1281f450a 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html @@ -1,18 +1,20 @@ - - - - -
- + diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts index 02fb5e8d75..805b9747a9 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts @@ -12,7 +12,7 @@ import { getSucceededRemoteData } from '../../../core/shared/operators'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model'; import { Subscription } from 'rxjs/internal/Subscription'; -import { PaginatedList } from '../../../core/data/paginated-list'; +import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component'; @Component({ selector: 'ds-metadata-representation-list', @@ -24,7 +24,7 @@ import { PaginatedList } from '../../../core/data/paginated-list'; * It expects an itemType to resolve the metadata to a an item * It expects a label to put on top of the list */ -export class MetadataRepresentationListComponent implements OnInit, OnDestroy { +export class MetadataRepresentationListComponent extends AbstractIncrementalListComponent> { /** * The parent of the list of related items to display */ @@ -45,13 +45,6 @@ export class MetadataRepresentationListComponent implements OnInit, OnDestroy { */ @Input() label: string; - /** - * The max amount of representations to display - * Defaults to 10 - * The default can optionally be overridden by providing the limit as input to the component - */ - @Input() limit = 10; - /** * The amount to increment the list by when clicking "view more" * Defaults to 10 @@ -59,60 +52,34 @@ export class MetadataRepresentationListComponent implements OnInit, OnDestroy { */ @Input() incrementBy = 10; - /** - * Is the list (re-)loading? - */ - loading = false; - - /** - * A list of metadata-representations to display - */ - representations$: Observable; - - /** - * The originally provided limit - * Used for resetting the limit to the original value when collapsing the list - */ - originalLimit: number; - /** * The total amount of metadata values available */ total: number; - /** - * Subscription on representations used to update the "loading" property of this component - */ - representationsSub: Subscription; - constructor(public relationshipService: RelationshipService) { - } - - ngOnInit(): void { - this.originalLimit = this.limit; - this.setRepresentations(); + super(); } /** - * Initialize the metadata representations + * Get a specific page + * @param page The page to fetch */ - setRepresentations() { + getPage(page: number): Observable { const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField); this.total = metadata.length; - this.representations$ = this.resolveMetadataRepresentations(metadata); - this.representationsSub = this.representations$.subscribe((represenations: MetadataRepresentation[]) => { - this.loading = represenations.length !== this.limit && represenations.length !== this.total; - }); + return this.resolveMetadataRepresentations(metadata, page); } /** * Resolve a list of metadata values to a list of metadata representations - * @param metadata + * @param metadata The list of all metadata values + * @param page The page to return representations for */ - resolveMetadataRepresentations(metadata: MetadataValue[]): Observable { + resolveMetadataRepresentations(metadata: MetadataValue[], page: number): Observable { return observableZip( ...metadata - .slice(0, this.limit) + .slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy) .map((metadatum: any) => Object.assign(new MetadataValue(), metadatum)) .map((metadatum: MetadataValue) => { if (metadatum.isVirtual) { @@ -137,33 +104,4 @@ export class MetadataRepresentationListComponent implements OnInit, OnDestroy { }) ); } - - /** - * Expand the list to display more metadata representations - */ - viewMore() { - this.limit = this.limit + this.incrementBy; - this.loading = true; - this.setRepresentations(); - } - - /** - * Collapse the list to display less metadata representations - */ - viewLess() { - if (this.limit > this.originalLimit) { - this.limit = this.limit - this.incrementBy; - } - this.loading = true; - this.setRepresentations(); - } - - /** - * Unsubscribe from the representations subscription - */ - ngOnDestroy(): void { - if (this.representationsSub) { - this.representationsSub.unsubscribe(); - } - } } diff --git a/src/app/+item-page/simple/related-items/related-items-component.ts b/src/app/+item-page/simple/related-items/related-items-component.ts index 5698e9ab1f..c9ef8ab2a0 100644 --- a/src/app/+item-page/simple/related-items/related-items-component.ts +++ b/src/app/+item-page/simple/related-items/related-items-component.ts @@ -1,12 +1,12 @@ -import { Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { Observable } from 'rxjs/internal/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { RelationshipService } from '../../../core/data/relationship.service'; import { FindListOptions } from '../../../core/data/request.models'; -import { Subscription } from 'rxjs/internal/Subscription'; import { ViewMode } from '../../../core/shared/view-mode.model'; +import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component'; @Component({ selector: 'ds-related-items', @@ -17,7 +17,7 @@ import { ViewMode } from '../../../core/shared/view-mode.model'; * This component is used for displaying relations between items * It expects a parent item and relationship type, as well as a label to display on top */ -export class RelatedItemsComponent implements OnInit, OnDestroy { +export class RelatedItemsComponent extends AbstractIncrementalListComponent>>> { /** * The parent of the list of related items to display */ @@ -29,13 +29,6 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { */ @Input() relationType: string; - /** - * The max amount of relations to display - * Defaults to 5 - * The default can optionally be overridden by providing the limit as input to the component - */ - @Input() limit = 5; - /** * The amount to increment the list by when clicking "view more" * Defaults to 5 @@ -54,77 +47,21 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { */ @Input() label: string; - /** - * Is the list (re-)loading? - */ - loading = false; - - /** - * The list of related items - */ - items$: Observable>>; - /** * The view-mode we're currently on - * @type {ElementViewMode} + * @type {ViewMode} */ viewMode = ViewMode.ListElement; - /** - * The originally provided limit - * Used for comparing the current size with the original - */ - originalLimit: number; - - /** - * Subscription on items used to update the "loading" property of this component - */ - itemSub: Subscription; - constructor(public relationshipService: RelationshipService) { - } - - ngOnInit(): void { - this.originalLimit = this.limit; - this.reloadItems(); + super(); } /** - * Reload the current list of items (using the current limit) + * Get a specific page + * @param page The page to fetch */ - reloadItems() { - this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.limit })); - this.itemSub = this.items$.subscribe((itemsRD: RemoteData>) => { - this.loading = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0); - }); - } - - /** - * Expand the list to display more - */ - viewMore() { - this.limit = this.limit + this.incrementBy; - this.loading = true; - this.reloadItems(); - } - - /** - * Collapse the list to display less - */ - viewLess() { - if (this.limit > this.originalLimit) { - this.limit = this.limit - this.incrementBy; - } - this.loading = true; - this.reloadItems(); - } - - /** - * Unsubscribe from the item subscription - */ - ngOnDestroy(): void { - if (this.itemSub) { - this.itemSub.unsubscribe(); - } + getPage(page: number): Observable>> { + return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.incrementBy, currentPage: page })); } } diff --git a/src/app/+item-page/simple/related-items/related-items.component.html b/src/app/+item-page/simple/related-items/related-items.component.html index d6251e2302..11cedc4040 100644 --- a/src/app/+item-page/simple/related-items/related-items.component.html +++ b/src/app/+item-page/simple/related-items/related-items.component.html @@ -1,18 +1,20 @@ - - - - -
- + From c31b1725a8ae9b43e6378dea5c4c6d556abd6628 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 9 Dec 2019 15:25:43 +0100 Subject: [PATCH 05/12] 66155: Fixed test cases and AoT build --- src/app/+item-page/item-page.module.ts | 4 ++- ...data-representation-list.component.spec.ts | 33 ++++++++----------- .../related-items.component.spec.ts | 33 +++++++++++-------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 940868a0c6..5c54becdde 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -28,6 +28,7 @@ import { MetadataValuesComponent } from './field-components/metadata-values/meta import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component'; import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; import { StatisticsModule } from '../statistics/statistics.module'; +import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component'; @NgModule({ imports: [ @@ -57,7 +58,8 @@ import { StatisticsModule } from '../statistics/statistics.module'; GenericItemPageFieldComponent, MetadataRepresentationListComponent, RelatedEntitiesSearchComponent, - TabbedRelatedEntitiesSearchComponent + TabbedRelatedEntitiesSearchComponent, + AbstractIncrementalListComponent ], exports: [ ItemComponent, diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts index d7fbd710aa..ad62ce4418 100644 --- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts +++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts @@ -8,6 +8,7 @@ import { Relationship } from '../../../core/shared/item-relationships/relationsh import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils'; import { TranslateModule } from '@ngx-translate/core'; import { VarDirective } from '../../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; const itemType = 'Person'; const metadataField = 'dc.contributor.author'; @@ -89,37 +90,29 @@ describe('MetadataRepresentationListComponent', () => { expect(fields.length).toBe(2); }); - it('should initialize the original limit', () => { - expect(comp.originalLimit).toEqual(comp.limit); + it('should contain one page of items', () => { + expect(comp.objects.length).toEqual(1); }); - describe('when viewMore is called', () => { - let originalLimit; - + describe('when increase is called', () => { beforeEach(() => { - // Store the original value of limit - originalLimit = comp.limit; - comp.viewMore(); + comp.increase(); }); - it('should increment the limit with incrementBy', () => { - expect(comp.limit).toEqual(originalLimit + comp.incrementBy); + it('should add a new page to the list', () => { + expect(comp.objects.length).toEqual(2); }); }); - describe('when viewLess is called', () => { - let originalLimit; - + describe('when decrease is called', () => { beforeEach(() => { - // Store the original value of limit - originalLimit = comp.limit; - // Increase limit with incrementBy - comp.limit = originalLimit + comp.incrementBy; - comp.viewLess(); + // Add a second page + comp.objects.push(observableOf(undefined)); + comp.decrease(); }); - it('should reset the limit to the original value', () => { - expect(comp.limit).toEqual(originalLimit); + it('should decrease the list of pages', () => { + expect(comp.objects.length).toEqual(1); }); }); diff --git a/src/app/+item-page/simple/related-items/related-items.component.spec.ts b/src/app/+item-page/simple/related-items/related-items.component.spec.ts index 71562c4398..5b1f33c64d 100644 --- a/src/app/+item-page/simple/related-items/related-items.component.spec.ts +++ b/src/app/+item-page/simple/related-items/related-items.component.spec.ts @@ -10,6 +10,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils import { RelationshipService } from '../../../core/data/relationship.service'; import { TranslateModule } from '@ngx-translate/core'; import { VarDirective } from '../../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; const parentItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), @@ -66,29 +67,33 @@ describe('RelatedItemsComponent', () => { expect(fields.length).toBe(mockItems.length); }); - describe('when viewMore is called', () => { + it('should contain one page of items', () => { + expect(comp.objects.length).toEqual(1); + }); + + describe('when increase is called', () => { beforeEach(() => { - comp.viewMore(); + comp.increase(); }); - it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => { - expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.limit + comp.incrementBy })); + it('should add a new page to the list', () => { + expect(comp.objects.length).toEqual(2); + }); + + it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => { + expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.incrementBy, currentPage: 2 })); }); }); - describe('when viewLess is called', () => { - let originalLimit; - + describe('when decrease is called', () => { beforeEach(() => { - // Store the original value of limit - originalLimit = comp.limit; - // Increase limit with incrementBy - comp.limit = originalLimit + comp.incrementBy; - comp.viewLess(); + // Add a second page + comp.objects.push(observableOf(undefined)); + comp.decrease(); }); - it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => { - expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: originalLimit })); + it('should decrease the list of pages', () => { + expect(comp.objects.length).toEqual(1); }); }); From 46c9a77cd97f2417b1f90df9953cb89fdd3a8d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 10 Dec 2019 18:05:48 +0000 Subject: [PATCH 06/12] adding default translations for funding lookup interface --- resources/i18n/en.json5 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 53db8e98c0..bd58b9dac7 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -1511,6 +1511,15 @@ "submission.general.save-later": "Save for later", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies", + + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding": "Search for Funding", + + "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency", + + "submission.sections.describe.relationship-lookup.title.Funding": "Funding", + + "submission.sections.general.add-more": "Add more", From 149f59039e10f49bdecb6d8d36a34f44c31fc965 Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 16 Dec 2019 11:19:08 +0100 Subject: [PATCH 07/12] fixed import issues --- .../search-page-routing.module.ts | 1 - src/app/+search-page/search-page.module.ts | 2 ++ .../+search-page/search-tracker.component.ts | 30 ++++++++++--------- src/app/+search-page/search.component.html | 4 +++ src/app/+search-page/search.component.ts | 17 ++++++----- src/app/app.effects.ts | 2 +- .../core/data/relationship-type.service.ts | 6 ++-- src/app/statistics/statistics.service.spec.ts | 3 +- src/app/statistics/statistics.service.ts | 30 +++++++++---------- 9 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index 083a1b4410..315e15a593 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { SearchComponent } from './search.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { SearchPageComponent } from './search-page.component'; diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index c3dbfe3196..e5ce670013 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -8,9 +8,11 @@ import { ConfigurationSearchPageComponent } from './configuration-search-page.co import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { SearchTrackerComponent } from './search-tracker.component'; import { StatisticsModule } from '../statistics/statistics.module'; +import { SearchComponent } from './search.component'; const components = [ SearchPageComponent, + SearchComponent, ConfigurationSearchPageComponent, SearchTrackerComponent ]; diff --git a/src/app/+search-page/search-tracker.component.ts b/src/app/+search-page/search-tracker.component.ts index e1df9b3905..58867e3f03 100644 --- a/src/app/+search-page/search-tracker.component.ts +++ b/src/app/+search-page/search-tracker.component.ts @@ -2,16 +2,17 @@ import { Component, Inject, OnInit } from '@angular/core'; import { Angulartics2 } from 'angulartics2'; import { filter, map, switchMap } from 'rxjs/operators'; import { SearchComponent } from './search.component'; -import { SearchService } from './search-service/search.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; import { HostWindowService } from '../shared/host-window.service'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { RouteService } from '../core/services/route.service'; import { hasValue } from '../shared/empty.util'; -import { SearchQueryResponse } from './search-service/search-query-response.model'; import { SearchSuccessResponse } from '../core/cache/response.models'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; +import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; +import { Router } from '@angular/router'; +import { SearchService } from '../core/shared/search/search.service'; +import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; +import { SearchQueryResponse } from '../shared/search/search-query-response.model'; /** * This component triggers a page view statistic @@ -30,14 +31,15 @@ import { PaginatedSearchOptions } from './paginated-search-options.model'; export class SearchTrackerComponent extends SearchComponent implements OnInit { constructor( - protected service:SearchService, - protected sidebarService:SidebarService, - protected windowService:HostWindowService, - @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService:SearchConfigurationService, - protected routeService:RouteService, - public angulartics2:Angulartics2 + protected service: SearchService, + protected sidebarService: SidebarService, + protected windowService: HostWindowService, + @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, + protected routeService: RouteService, + public angulartics2: Angulartics2, + protected router: Router ) { - super(service, sidebarService, windowService, searchConfigService, routeService); + super(service, sidebarService, windowService, searchConfigService, routeService, router); } ngOnInit():void { @@ -58,9 +60,9 @@ export class SearchTrackerComponent extends SearchComponent implements OnInit { ) ) .subscribe((entry) => { - const config:PaginatedSearchOptions = entry.searchOptions; - const searchQueryResponse:SearchQueryResponse = entry.response; - const filters:Array<{ filter:string, operator:string, value:string, label:string; }> = []; + const config: PaginatedSearchOptions = entry.searchOptions; + const searchQueryResponse: SearchQueryResponse = entry.response; + const filters:Array<{ filter: string, operator: string, value: string, label: string; }> = []; const appliedFilters = searchQueryResponse.appliedFilters || []; for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) { const appliedFilter = appliedFilters[i]; diff --git a/src/app/+search-page/search.component.html b/src/app/+search-page/search.component.html index 9423062bde..a6e83d2b64 100644 --- a/src/app/+search-page/search.component.html +++ b/src/app/+search-page/search.component.html @@ -46,5 +46,9 @@ [scopes]="(scopeListRD$ | async)" [inPlaceSearch]="inPlaceSearch"> +
+
+
+
diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index cc763e253f..bfb99755d8 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -1,20 +1,22 @@ import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { startWith, switchMap, } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list'; import { RemoteData } from '../core/data/remote-data'; import { DSpaceObject } from '../core/shared/dspace-object.model'; import { pushInOut } from '../shared/animations/push'; import { HostWindowService } from '../shared/host-window.service'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { SearchResult } from './search-result.model'; -import { SearchService } from './search-service/search.service'; import { SidebarService } from '../shared/sidebar/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 '../core/services/route.service'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; +import { SearchResult } from '../shared/search/search-result.model'; +import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; +import { SearchService } from '../core/shared/search/search.service'; +import { currentPath } from '../shared/utils/route.utils'; +import { Router } from '@angular/router'; @Component({ selector: 'ds-search', @@ -96,7 +98,8 @@ export class SearchComponent implements OnInit { protected sidebarService: SidebarService, protected windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, - protected routeService: RouteService) { + protected routeService: RouteService, + protected router: Router) { this.isXsOrSm$ = this.windowService.isXsOrSm(); } @@ -159,7 +162,7 @@ export class SearchComponent implements OnInit { */ private getSearchLink(): string { if (this.inPlaceSearch) { - return './'; + return currentPath(this.router); } return this.service.getSearchLink(); } diff --git a/src/app/app.effects.ts b/src/app/app.effects.ts index 22df36e1ac..64573609c7 100644 --- a/src/app/app.effects.ts +++ b/src/app/app.effects.ts @@ -1,8 +1,8 @@ import { StoreEffects } from './store.effects'; import { NotificationsEffects } from './shared/notifications/notifications.effects'; import { NavbarEffects } from './navbar/navbar.effects'; -import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects'; import { SidebarEffects } from './shared/sidebar/sidebar-effects.service'; +import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects'; export const appEffects = [ StoreEffects, diff --git a/src/app/core/data/relationship-type.service.ts b/src/app/core/data/relationship-type.service.ts index a30dd5d57e..627fc4863f 100644 --- a/src/app/core/data/relationship-type.service.ts +++ b/src/app/core/data/relationship-type.service.ts @@ -4,7 +4,6 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { filter, find, map, switchMap, tap } from 'rxjs/operators'; import { configureRequest, getSucceededRemoteData } from '../shared/operators'; -import { FindAllOptions, FindAllRequest } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; import { RemoteData } from './remote-data'; @@ -12,6 +11,7 @@ import { PaginatedList } from './paginated-list'; import { combineLatest as observableCombineLatest } from 'rxjs'; import { ItemType } from '../shared/item-relationships/item-type.model'; import { isNotUndefined } from '../../shared/empty.util'; +import { FindListOptions, FindListRequest } from './request.models'; /** * The service handling all relationship requests @@ -35,11 +35,11 @@ export class RelationshipTypeService { ); } - getAllRelationshipTypes(options: FindAllOptions): Observable>> { + getAllRelationshipTypes(options: FindListOptions): Observable>> { const link$ = this.halService.getEndpoint(this.linkPath); return link$ .pipe( - map((endpointURL: string) => new FindAllRequest(this.requestService.generateRequestId(), endpointURL, options)), + map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)), configureRequest(this.requestService), switchMap(() => this.rdbService.buildList(link$)) ); diff --git a/src/app/statistics/statistics.service.spec.ts b/src/app/statistics/statistics.service.spec.ts index 3a416968f8..c6cc4c10b5 100644 --- a/src/app/statistics/statistics.service.spec.ts +++ b/src/app/statistics/statistics.service.spec.ts @@ -3,10 +3,9 @@ import { RequestService } from '../core/data/request.service'; import { HALEndpointServiceStub } from '../shared/testing/hal-endpoint-service-stub'; import { getMockRequestService } from '../shared/mocks/mock-request.service'; import { TrackRequest } from './track-request.model'; -import { ResourceType } from '../core/shared/resource-type'; -import { SearchOptions } from '../+search-page/search-options.model'; import { isEqual } from 'lodash'; import { DSpaceObjectType } from '../core/shared/dspace-object-type.model'; +import { SearchOptions } from '../shared/search/search-options.model'; describe('StatisticsService', () => { let service:StatisticsService; diff --git a/src/app/statistics/statistics.service.ts b/src/app/statistics/statistics.service.ts index cd89125b3c..004e013164 100644 --- a/src/app/statistics/statistics.service.ts +++ b/src/app/statistics/statistics.service.ts @@ -3,10 +3,10 @@ import { Injectable } from '@angular/core'; import { DSpaceObject } from '../core/shared/dspace-object.model'; import { map, take } from 'rxjs/operators'; import { TrackRequest } from './track-request.model'; -import { SearchOptions } from '../+search-page/search-options.model'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { HALEndpointService } from '../core/shared/hal-endpoint.service'; import { RestRequest } from '../core/data/request.models'; +import { SearchOptions } from '../shared/search/search-options.model'; /** * The statistics service @@ -15,24 +15,24 @@ import { RestRequest } from '../core/data/request.models'; export class StatisticsService { constructor( - protected requestService:RequestService, - protected halService:HALEndpointService, + protected requestService: RequestService, + protected halService: HALEndpointService, ) { } - private sendEvent(linkPath:string, body:any) { + private sendEvent(linkPath: string, body: any) { const requestId = this.requestService.generateRequestId(); this.halService.getEndpoint(linkPath).pipe( - map((endpoint:string) => new TrackRequest(requestId, endpoint, JSON.stringify(body))), + map((endpoint: string) => new TrackRequest(requestId, endpoint, JSON.stringify(body))), take(1) // otherwise the previous events will fire again - ).subscribe((request:RestRequest) => this.requestService.configure(request)); + ).subscribe((request: RestRequest) => this.requestService.configure(request)); } /** * To track a page view * @param dso: The dso which was viewed */ - trackViewEvent(dso:DSpaceObject) { + trackViewEvent(dso: DSpaceObject) { this.sendEvent('/statistics/viewevents', { targetId: dso.uuid, targetType: (dso as any).type @@ -47,10 +47,10 @@ export class StatisticsService { * @param filters: An array of search filters used to filter the result set */ trackSearchEvent( - searchOptions:SearchOptions, - page:{ size:number, totalElements:number, totalPages:number, number:number }, - sort:{ by:string, order:string }, - filters?:Array<{ filter:string, operator:string, value:string, label:string }> + searchOptions: SearchOptions, + page: { size: number, totalElements: number, totalPages: number, number: number }, + sort: { by: string, order: string }, + filters?: Array<{ filter: string, operator: string, value: string, label: string }> ) { const body = { query: searchOptions.query, @@ -66,13 +66,13 @@ export class StatisticsService { }, }; if (hasValue(searchOptions.configuration)) { - Object.assign(body, {configuration: searchOptions.configuration}) + Object.assign(body, { configuration: searchOptions.configuration }) } if (hasValue(searchOptions.dsoType)) { - Object.assign(body, {dsoType: searchOptions.dsoType.toLowerCase()}) + Object.assign(body, { dsoType: searchOptions.dsoType.toLowerCase() }) } if (hasValue(searchOptions.scope)) { - Object.assign(body, {scope: searchOptions.scope}) + Object.assign(body, { scope: searchOptions.scope }) } if (isNotEmpty(filters)) { const bodyFilters = []; @@ -85,7 +85,7 @@ export class StatisticsService { label: filter.label }) } - Object.assign(body, {appliedFilters: bodyFilters}) + Object.assign(body, { appliedFilters: bodyFilters }) } this.sendEvent('/statistics/searchevents', body); } From 2db42d067718a3ad9ff118f96ebfbceca7204f3f Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 17 Dec 2019 13:47:09 +0100 Subject: [PATCH 08/12] added tests --- config/environment.default.js | 9 +- .../models/disabled/dynamic-disabled.model.ts | 1 - .../parsers/disabled-field-parser.spec.ts | 66 +++++++++++ .../builder/parsers/disabled-field-parser.ts | 2 + .../selectable-list.reducer.spec.ts | 112 ++++++++++++++++++ .../selectable-list.service.spec.ts | 98 +++++++++++++++ 6 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts create mode 100644 src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts create mode 100644 src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts diff --git a/config/environment.default.js b/config/environment.default.js index 24386d6cf7..df4f89a2fe 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -9,11 +9,10 @@ module.exports = { }, // The REST API server settings. rest: { - ssl: true, - host: 'dspace7.4science.cloud', - port: 443, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server/api' + ssl: true, + host: 'dspace7-entities.atmire.com', + port: 443, + nameSpace: '/server/api' }, // Caching settings cache: { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts index eb1f3660e6..05de204fa8 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts @@ -14,7 +14,6 @@ export class DynamicDisabledModel extends DsDynamicInputModel { constructor(config: DsDynamicDisabledModelConfig, layout?: DynamicFormControlLayout) { super(config, layout); - this.readOnly = true; this.disabled = true; this.valueUpdates.next(config.value); diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts new file mode 100644 index 0000000000..7dce05f18d --- /dev/null +++ b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts @@ -0,0 +1,66 @@ +import { FormFieldModel } from '../models/form-field.model'; +import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; +import { ParserOptions } from './parser-options'; +import { DisabledFieldParser } from './disabled-field-parser'; +import { DynamicDisabledModel } from '../ds-dynamic-form-ui/models/disabled/dynamic-disabled.model'; + +describe('DisabledFieldParser test suite', () => { + let field: FormFieldModel; + let initFormValues: any = {}; + + const submissionId = '1234'; + const parserOptions: ParserOptions = { + readOnly: false, + submissionScope: null, + authorityUuid: null + }; + + beforeEach(() => { + field = { + input: { + type: '' + }, + label: 'Description', + mandatory: 'false', + repeatable: false, + hints: 'Enter a description.', + selectableMetadata: [ + { + metadata: 'description' + } + ], + languageCodes: [] + } as FormFieldModel; + + }); + + it('should init parser properly', () => { + const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions); + + expect(parser instanceof DisabledFieldParser).toBe(true); + }); + + it('should return a DynamicDisabledModel object when repeatable option is false', () => { + const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions); + + const fieldModel = parser.parse(); + + expect(fieldModel instanceof DynamicDisabledModel).toBe(true); + }); + + it('should set init value properly', () => { + initFormValues = { + description: [ + new FormFieldMetadataValueObject('test description'), + ], + }; + const expectedValue ='test description'; + + const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions); + + const fieldModel = parser.parse(); + console.log(fieldModel); + expect(fieldModel.value).toEqual(expectedValue); + }); + +}); diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.ts index 5cccff4591..49d31c6118 100644 --- a/src/app/shared/form/builder/parsers/disabled-field-parser.ts +++ b/src/app/shared/form/builder/parsers/disabled-field-parser.ts @@ -5,7 +5,9 @@ import { DsDynamicDisabledModelConfig, DynamicDisabledModel } from '../ds-dynami export class DisabledFieldParser extends FieldParser { public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any { + console.log(fieldValue); const emptyModelConfig: DsDynamicDisabledModelConfig = this.initModel(null, label); + this.setValues(emptyModelConfig, fieldValue); return new DynamicDisabledModel(emptyModelConfig) } } diff --git a/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts b/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts new file mode 100644 index 0000000000..29b60cdc02 --- /dev/null +++ b/src/app/shared/object-list/selectable-list/selectable-list.reducer.spec.ts @@ -0,0 +1,112 @@ +import { + SelectableListAction, + SelectableListDeselectAction, SelectableListDeselectAllAction, + SelectableListDeselectSingleAction, + SelectableListSelectAction, + SelectableListSelectSingleAction, + SelectableListSetSelectionAction +} from './selectable-list.actions'; +import { selectableListReducer } from './selectable-list.reducer'; +import { ListableObject } from '../../object-collection/shared/listable-object.model'; +import { hasValue } from '../../empty.util'; + +// tslint:disable:max-classes-per-file +class SelectableObject extends ListableObject { + constructor(private value: string) { + super(); + } + + equals(other: SelectableObject): boolean { + return hasValue(this.value) && hasValue(other.value) && this.value === other.value; + } + + getRenderTypes() { + return ['selectable']; + } +} + +class NullAction extends SelectableListAction { + type = null; + + constructor() { + super(undefined, undefined); + } +} + +// tslint:enable:max-classes-per-file +const listID1 = 'id1'; +const listID2 = 'id2'; +const value1 = 'Selected object'; +const value2 = 'Another selected object'; +const value3 = 'Selection'; +const value4 = 'Selected object numero 4'; + +const selected1 = new SelectableObject(value1); +const selected2 = new SelectableObject(value2); +const selected3 = new SelectableObject(value3); +const selected4 = new SelectableObject(value4); +const testState = { [listID1]: { id: listID1, selection: [selected1, selected2] } }; + +describe('selectableListReducer', () => { + + it('should return the current state when no valid actions have been made', () => { + const state = {}; + state[listID1] = {}; + state[listID1] = { id: listID1, selection: [selected1, selected2] }; + const action = new NullAction(); + const newState = selectableListReducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should start with an empty object', () => { + const state = {}; + const action = new NullAction(); + const newState = selectableListReducer(undefined, action); + + expect(newState).toEqual(state); + }); + + it('should add the payload to the existing list in response to the SELECT action for the given id', () => { + const action = new SelectableListSelectAction(listID1, [selected3, selected4]); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual([selected1, selected2, selected3, selected4]); + }); + + it('should add the payload to the existing list in response to the SELECT_SINGLE action for the given id', () => { + const action = new SelectableListSelectSingleAction(listID1, selected4); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual([selected1, selected2, selected4]); + }); + + it('should remove the payload from the existing list in response to the DESELECT action for the given id', () => { + const action = new SelectableListDeselectAction(listID1, [selected1, selected2]); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual([]); + }); + + it('should remove the payload from the existing list in response to the DESELECT_SINGLE action for the given id', () => { + const action = new SelectableListDeselectSingleAction(listID1, selected2); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual([selected1]); + }); + + it('should set the list to the payload in response to the SET_SELECTION action for the given id', () => { + const action = new SelectableListSetSelectionAction(listID2, [selected2, selected4]); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual(testState[listID1].selection); + expect(newState[listID2].selection).toEqual([selected2, selected4]); + }); + + it('should remove the payload from the existing list in response to the DESELECT action for the given id', () => { + const action = new SelectableListDeselectAllAction(listID1); + const newState = selectableListReducer(testState, action); + + expect(newState[listID1].selection).toEqual([]); + }); +}); diff --git a/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts new file mode 100644 index 0000000000..7699541fe3 --- /dev/null +++ b/src/app/shared/object-list/selectable-list/selectable-list.service.spec.ts @@ -0,0 +1,98 @@ +import { Store } from '@ngrx/store'; +import { async, TestBed } from '@angular/core/testing'; +import { SelectableListService } from './selectable-list.service'; +import { SelectableListsState } from './selectable-list.reducer'; +import { ListableObject } from '../../object-collection/shared/listable-object.model'; +import { hasValue } from '../../empty.util'; +import { SelectableListDeselectAction, SelectableListDeselectSingleAction, SelectableListSelectAction, SelectableListSelectSingleAction } from './selectable-list.actions'; + +class SelectableObject extends ListableObject { + constructor(private value: string) { + super(); + } + + equals(other: SelectableObject): boolean { + return hasValue(this.value) && hasValue(other.value) && this.value === other.value; + } + + getRenderTypes() { + return ['selectable']; + } +} + +describe('SelectableListService', () => { + const listID1 = 'id1'; + const value1 = 'Selected object'; + const value2 = 'Another selected object'; + const value3 = 'Selection'; + const value4 = 'Selected object numero 4'; + + const selected1 = new SelectableObject(value1); + const selected2 = new SelectableObject(value2); + const selected3 = new SelectableObject(value3); + const selected4 = new SelectableObject(value4); + + let service: SelectableListService; + const store: Store = jasmine.createSpyObj('store', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + }); + beforeEach(async(() => { + TestBed.configureTestingModule({ + + providers: [ + { + provide: Store, useValue: store + } + ] + }).compileComponents(); + })); + + beforeEach(() => { + service = new SelectableListService(store); + }); + + describe('when the selectSingle method is triggered', () => { + beforeEach(() => { + service.selectSingle(listID1, selected3); + }); + + it('SelectableListSelectSingleAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SelectableListSelectSingleAction(listID1, selected3)); + }); + + }); + + describe('when the select method is triggered', () => { + beforeEach(() => { + service.select(listID1, [selected1, selected4]); + }); + + it('SelectableListSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SelectableListSelectAction(listID1, [selected1, selected4])); + }); + }); + + describe('when the deselectSingle method is triggered', () => { + beforeEach(() => { + service.deselectSingle(listID1, selected4); + }); + + it('SelectableListDeselectSingleAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SelectableListDeselectSingleAction(listID1, selected4)); + }); + + }); + + describe('when the deselect method is triggered', () => { + beforeEach(() => { + service.deselect(listID1, [selected2, selected4]); + }); + + it('SelectableListDeselectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SelectableListDeselectAction(listID1, [selected2, selected4])); + }); + }); + +}); From c480e9604396a933191b106b9d3da2920fdb33fd Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 18 Dec 2019 14:38:17 +0100 Subject: [PATCH 09/12] more tests --- .../name-variant-modal.component.html | 4 +- .../name-variant-modal.component.spec.ts | 26 +++++- .../dynamic-disabled.component.spec.ts | 58 ++++++++++++ .../disabled/dynamic-disabled.component.ts | 4 +- ...electable-list-item-control.component.html | 6 +- ...ctable-list-item-control.component.spec.ts | 91 +++++++++++++++++++ .../selectable-list-item-control.component.ts | 39 ++++---- .../search-form/search-form.component.spec.ts | 1 - 8 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts create mode 100644 src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html index 9d2139621c..13ae884ccb 100644 --- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html +++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.html @@ -8,6 +8,6 @@ {{'submission.sections.describe.relationship-lookup.name-variant.notification.content' | translate: { value: value } }}
diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts index 4af7b8161a..b5043ea2d6 100644 --- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts +++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.spec.ts @@ -3,16 +3,23 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NameVariantModalComponent } from './name-variant-modal.component'; import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; describe('NameVariantModalComponent', () => { let component: NameVariantModalComponent; let fixture: ComponentFixture; + let debugElement; + let modal; + function init() { + modal = jasmine.createSpyObj('modal', ['close', 'dismiss']); + } beforeEach(async(() => { + init(); TestBed.configureTestingModule({ declarations: [NameVariantModalComponent], imports: [NgbModule.forRoot(), TranslateModule.forRoot()], - providers: [NgbActiveModal] + providers: [{ provide: NgbActiveModal, useValue: modal }] }) .compileComponents(); })); @@ -20,10 +27,27 @@ describe('NameVariantModalComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(NameVariantModalComponent); component = fixture.componentInstance; + debugElement = fixture.debugElement; fixture.detectChanges(); + }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('when close button is clicked, dismiss should be called on the modal', () => { + debugElement.query(By.css('button.close')).triggerEventHandler('click', {}); + expect(modal.dismiss).toHaveBeenCalled(); + }); + + it('when confirm button is clicked, close should be called on the modal', () => { + debugElement.query(By.css('button.confirm-button')).triggerEventHandler('click', {}); + expect(modal.close).toHaveBeenCalled(); + }); + + it('when decline button is clicked, dismiss should be called on the modal', () => { + debugElement.query(By.css('button.decline-button')).triggerEventHandler('click', {}); + expect(modal.dismiss).toHaveBeenCalled(); + }); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts new file mode 100644 index 0000000000..8e0c6fc20e --- /dev/null +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts @@ -0,0 +1,58 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { DsDynamicDisabledComponent } from './dynamic-disabled.component'; +import { FormsModule } from '@angular/forms'; +import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; +import { DynamicDisabledModel } from './dynamic-disabled.model'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('DsDynamicDisabledComponent', () => { + let comp: DsDynamicDisabledComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + let model; + + function init() { + model = new DynamicDisabledModel({ value: 'test', repeatable: false, metadataFields: [], submissionId: '1234', id: '1' }); + } + + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + declarations: [DsDynamicDisabledComponent], + imports: [FormsModule, TranslateModule.forRoot()], + providers: [ + { + provide: DynamicFormLayoutService, + useValue: {} + }, + { + provide: DynamicFormValidationService, + useValue: {} + } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DsDynamicDisabledComponent); + comp = fixture.componentInstance; // DsDynamicDisabledComponent test instance + de = fixture.debugElement; + el = de.nativeElement; + comp.model = model; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(comp).toBeTruthy(); + }); + + it('should have a disabled input', () => { + const input = de.query(By.css('input')); + console.log(input.nativeElement.getAttribute('disabled')); + expect(input.nativeElement.getAttribute('disabled')).toEqual(''); + }); +}); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts index 173509acf9..f2553efb44 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts @@ -3,7 +3,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { FormGroup } from '@angular/forms'; import { DynamicDisabledModel } from './dynamic-disabled.model'; -import { RelationshipTypeService } from '../../../../../../core/data/relationship-type.service'; @Component({ selector: 'ds-dynamic-disabled', @@ -21,8 +20,7 @@ export class DsDynamicDisabledComponent extends DynamicFormControlComponent { @Output() focus: EventEmitter = new EventEmitter(); constructor(protected layoutService: DynamicFormLayoutService, - protected validationService: DynamicFormValidationService, - protected relationshipTypeService: RelationshipTypeService + protected validationService: DynamicFormValidationService ) { super(layoutService, validationService); } diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html index 4455cedeb9..92d85d03f4 100644 --- a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html +++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html @@ -3,10 +3,10 @@ [name]="'checkbox' + index" [id]="'object' + index" [ngModel]="selected$ | async" - (ngModelChange)="selectCheckbox($event, object)"> + (ngModelChange)="selectCheckbox($event)"> -
\ No newline at end of file + (click)="selectRadio(!checked)"> + diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts new file mode 100644 index 0000000000..25cf6b15f0 --- /dev/null +++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.spec.ts @@ -0,0 +1,91 @@ +import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core/testing'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { SelectableListService } from '../../../object-list/selectable-list/selectable-list.service'; +import { SelectableListItemControlComponent } from './selectable-list-item-control.component'; +import { Item } from '../../../../core/shared/item.model'; +import { FormsModule } from '@angular/forms'; +import { VarDirective } from '../../../utils/var.directive'; +import { of as observableOf } from 'rxjs'; +import { ListableObject } from '../listable-object.model'; + +describe('SelectableListItemControlComponent', () => { + let comp: SelectableListItemControlComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + let object; + let otherObject; + let selectionConfig; + let listId; + let index; + let selectionService; + let selection: ListableObject[]; + let uuid1: string; + let uuid2: string; + + function init() { + uuid1 = '0beb44f8-d2ed-459a-a1e7-ffbe059089a9'; + uuid2 = 'e1dc80aa-c269-4aa5-b6bd-008d98056247'; + listId = 'Test List ID'; + object = Object.assign(new Item(), {uuid: uuid1}); + otherObject = Object.assign(new Item(), {uuid: uuid2}); + selectionConfig = {repeatable: false, listId}; + index = 0; + selection = [otherObject]; + selectionService = jasmine.createSpyObj('selectionService', { + selectSingle: jasmine.createSpy('selectSingle'), + deselectSingle: jasmine.createSpy('deselectSingle'), + isObjectSelected: observableOf(true), + getSelectableList: observableOf({ selection }) + } + ); + } + + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + declarations: [SelectableListItemControlComponent, VarDirective], + imports: [FormsModule], + providers: [ + { + provide: SelectableListService, + useValue: selectionService + } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectableListItemControlComponent); + comp = fixture.componentInstance; // SelectableListItemControlComponent test instance + de = fixture.debugElement; + el = de.nativeElement; + comp.object = object; + comp.selectionConfig = selectionConfig; + comp.index = index; + fixture.detectChanges(); + }); + + it('should call deselectSingle on the service when the object when selectCheckbox is called with value false', () => { + comp.selectCheckbox(false); + expect(selectionService.deselectSingle).toHaveBeenCalledWith(listId, object); + }); + + it('should call selectSingle on the service when the object when selectCheckbox is called with value false', () => { + comp.selectCheckbox(true); + expect(selectionService.selectSingle).toHaveBeenCalledWith(listId, object); + }); + + it('should call selectSingle on the service when the object when selectRadio is called with value true and deselect all others in the selection', () => { + comp.selectRadio(true ); + expect(selectionService.deselectSingle).toHaveBeenCalledWith(listId, selection[0]); + expect(selectionService.selectSingle).toHaveBeenCalledWith(listId, object); + }); + + it('should not call selectSingle on the service when the object when selectRadio is called with value false and not deselect all others in the selection', () => { + comp.selectRadio(false ); + expect(selectionService.deselectSingle).not.toHaveBeenCalledWith(listId, selection[0]); + expect(selectionService.selectSingle).not.toHaveBeenCalledWith(listId, object); + }); +}); diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts index 735f06fe5f..d1536c56e6 100644 --- a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts +++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.ts @@ -49,29 +49,30 @@ export class SelectableListItemControlComponent implements OnInit { }) } - selectCheckbox(value: boolean, object: ListableObject) { + selectCheckbox(value: boolean) { if (value) { - this.selectionService.selectSingle(this.selectionConfig.listId, object); + this.selectionService.selectSingle(this.selectionConfig.listId, this.object); } else { - this.selectionService.deselectSingle(this.selectionConfig.listId, object); + this.selectionService.deselectSingle(this.selectionConfig.listId, this.object); } } - selectRadio(value: boolean, object: ListableObject) { - const selected$ = this.selectionService.getSelectableList(this.selectionConfig.listId); - selected$.pipe( - take(1), - map((selected) => selected ? selected.selection : []) - ).subscribe((selection) => { - // First deselect any existing selections, this is a radio button - selection.forEach((selectedObject) => { - this.selectionService.deselectSingle(this.selectionConfig.listId, selectedObject); - this.deselectObject.emit(selectedObject); - }); - if (value) { - this.selectionService.selectSingle(this.selectionConfig.listId, object); - this.selectObject.emit(object); - } - }); + selectRadio(value: boolean) { + if (value) { + const selected$ = this.selectionService.getSelectableList(this.selectionConfig.listId); + selected$.pipe( + take(1), + map((selected) => selected ? selected.selection : []) + ).subscribe((selection) => { + // First deselect any existing selections, this is a radio button + selection.forEach((selectedObject) => { + this.selectionService.deselectSingle(this.selectionConfig.listId, selectedObject); + this.deselectObject.emit(selectedObject); + }); + this.selectionService.selectSingle(this.selectionConfig.listId, this.object); + this.selectObject.emit(this.object); + } + ); + } } } diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index 03081e909e..74ed4bb913 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -3,7 +3,6 @@ import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { SearchFormComponent } from './search-form.component'; import { FormsModule } from '@angular/forms'; -import { ResourceType } from '../../core/shared/resource-type'; import { RouterTestingModule } from '@angular/router/testing'; import { Community } from '../../core/shared/community.model'; import { TranslateModule } from '@ngx-translate/core'; From 0dbea31b0db872c12c3643675392fc4adce13b25 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 19 Dec 2019 09:23:13 +0100 Subject: [PATCH 10/12] added typedoc --- src/app/core/data/relationship.service.ts | 49 +++++++++++++++++++ src/app/core/services/route.service.ts | 3 ++ src/app/core/utilities/equals.decorators.ts | 13 +++++ src/app/core/utilities/equatable.ts | 10 ++++ ...esult-list-submission-element.component.ts | 2 +- .../name-variant-modal.component.ts | 4 ++ ...ynamic-form-control-container.component.ts | 13 +++-- .../disabled/dynamic-disabled.component.ts | 3 ++ .../models/disabled/dynamic-disabled.model.ts | 3 ++ ...dynamic-lookup-relation-modal.component.ts | 3 ++ .../name-variant.actions.ts | 10 +++- .../relationship.effects.ts | 5 ++ ...ic-lookup-relation-search-tab.component.ts | 23 +++++++++ ...lookup-relation-selection-tab.component.ts | 9 ++++ .../models/relationship-options.model.ts | 3 ++ .../builder/parsers/disabled-field-parser.ts | 3 ++ .../form/builder/parsers/parser-factory.ts | 3 ++ .../shared/form/builder/parsers/row-parser.ts | 4 ++ .../selectable-list.actions.ts | 21 +++++++- .../selectable-list.reducer.ts | 35 +++++++++++++ 20 files changed, 212 insertions(+), 7 deletions(-) diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index ec7dd7fc8d..d624238bb8 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -92,6 +92,14 @@ export class RelationshipService extends DataService { ); } + /** + * Method to create a new relationship + * @param typeId The identifier of the relationship type + * @param item1 The first item of the relationship + * @param item2 The second item of the relationship + * @param leftwardValue The leftward value of the relationship + * @param rightwardValue The rightward value of the relationship + */ addRelationship(typeId: string, item1: Item, item2: Item, leftwardValue?: string, rightwardValue?: string): Observable { const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); @@ -112,6 +120,10 @@ export class RelationshipService extends DataService { ); } + /** + * Method to remove two items of a relationship from the cache using the identifier of the relationship + * @param relationshipId The identifier of the relationship + */ private removeRelationshipItemsFromCacheByRelationship(relationshipId: string) { this.findById(relationshipId).pipe( getSucceededRemoteData(), @@ -128,6 +140,10 @@ export class RelationshipService extends DataService { }) } + /** + * Method to remove an item that's part of a relationship from the cache + * @param item The item to remove from the cache + */ private removeRelationshipItemsFromCache(item) { this.objectCache.remove(item.self); this.requestService.removeByHrefSubstring(item.self); @@ -258,6 +274,12 @@ export class RelationshipService extends DataService { ); } + /** + * Method to retrieve a relationship based on two items and a relationship type label + * @param item1 The first item in the relationship + * @param item2 The second item in the relationship + * @param label The rightward or leftward type of the relationship + */ getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string): Observable { return this.getItemRelationshipsByLabel(item1, label) .pipe( @@ -287,24 +309,51 @@ export class RelationshipService extends DataService { ); } + /** + * Method to set the name variant for specific list and item + * @param listID The list for which to save the name variant + * @param itemID The item ID for which to save the name variant + * @param nameVariant The name variant to save + */ public setNameVariant(listID: string, itemID: string, nameVariant: string) { this.appStore.dispatch(new SetNameVariantAction(listID, itemID, nameVariant)); } + /** + * Method to retrieve the name variant for a specific list and item + * @param listID The list for which to retrieve the name variant + * @param itemID The item ID for which to retrieve the name variant + */ public getNameVariant(listID: string, itemID: string): Observable { return this.appStore.pipe( select(relationshipStateSelector(listID, itemID)) ); } + /** + * Method to remove the name variant for specific list and item + * @param listID The list for which to remove the name variant + * @param itemID The item ID for which to remove the name variant + */ public removeNameVariant(listID: string, itemID: string) { this.appStore.dispatch(new RemoveNameVariantAction(listID, itemID)); } + /** + * Method to retrieve all name variants for a single list + * @param listID The id of the list + */ public getNameVariantsByListID(listID: string) { return this.appStore.pipe(select(relationshipListStateSelector(listID))); } + /** + * Method to update the name variant on the server + * @param item1 The first item of the relationship + * @param item2 The second item of the relationship + * @param relationshipLabel The leftward or rightward type of the relationship + * @param nameVariant The name variant to set for the matching relationship + */ public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable> { return this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel) .pipe( diff --git a/src/app/core/services/route.service.ts b/src/app/core/services/route.service.ts index 8bca76f7d2..b29c491cb0 100644 --- a/src/app/core/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -195,6 +195,9 @@ export class RouteService { this.store.dispatch(new SetParameterAction(key, value)); } + /** + * Sets the current route parameters and query parameters in the store + */ public setCurrentRouteInfo() { combineLatest(this.getRouteParams(), this.route.queryParams) .pipe(take(1)) diff --git a/src/app/core/utilities/equals.decorators.ts b/src/app/core/utilities/equals.decorators.ts index 6dde05922e..6fdbd40c0f 100644 --- a/src/app/core/utilities/equals.decorators.ts +++ b/src/app/core/utilities/equals.decorators.ts @@ -5,6 +5,10 @@ import { EquatableObject } from './equatable'; const excludedFromEquals = new Map(); const fieldsForEqualsMap = new Map(); +/** + * Decorator function that adds the equatable settings from the given (parent) object + * @param parentCo The constructor of the parent object + */ export function inheritEquatable(parentCo: GenericConstructor>) { return function decorator(childCo: GenericConstructor>) { const parentExcludedFields = getExcludedFromEqualsFor(parentCo) || []; @@ -21,6 +25,11 @@ export function inheritEquatable(parentCo: GenericConstructor { if (object1[key] === object2[key]) { @@ -27,6 +33,10 @@ function equalsByFields(object1, object2, fieldList): boolean { return hasNoValue(unequalProperty); } +/** + * Abstract class to represent objects that can be compared to each other + * It provides a default way of comparing + */ export abstract class EquatableObject { equals(other: T): boolean { if (hasNoValue(other)) { diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts index 4a2a41aae5..cbddb8d6f9 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts @@ -24,7 +24,7 @@ import { NameVariantModalComponent } from '../../name-variant-modal/name-variant }) /** - * The component for displaying a list element for an item search result of the type Person + * The component for displaying a list element for an item search result of the type OrgUnit */ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchResultListElementComponent implements OnInit { allSuggestions: string[]; diff --git a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts index 34eab47b47..75817d786a 100644 --- a/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts +++ b/src/app/entity-groups/research-entities/submission/name-variant-modal/name-variant-modal.component.ts @@ -1,6 +1,10 @@ import { Component, Input } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +/** + * This component a pop up for when the user selects a custom name variant during submission for a relationship$ + * The user can either choose to decline or accept to save the name variant as a metadata in the entity + */ @Component({ selector: 'ds-name-variant-modal', templateUrl: './name-variant-modal.component.html', diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index b78385dc62..2079f23725 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -230,6 +230,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo super(componentFactoryResolver, layoutService, validationService); } + /** + * Sets up the necessary variables for when this control can be used to add relationships to the submitted item + */ ngOnInit(): void { this.hasRelationLookup = hasValue(this.model.relationship); if (this.hasRelationLookup) { @@ -314,6 +317,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo return this.model.value.pipe(map((list: Array>) => isNotEmpty(list))); } + /** + * Open a modal where the user can select relationships to be added to item being submitted + */ openLookup() { this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, { size: 'lg' @@ -327,12 +333,13 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo modalComp.item = this.item; } + /** + * Method to remove a selected relationship from the item + * @param object The second item in the relationship, the submitted item being the first + */ removeSelection(object: SearchResult) { this.selectableListService.deselectSingle(this.listId, object); this.store.dispatch(new RemoveRelationshipAction(this.item, object.indexableObject, this.model.relationship.relationshipType)) - - // this.zone.runOutsideAngular( - // () => ); } /** diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts index f2553efb44..490be050ef 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.ts @@ -4,6 +4,9 @@ import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValid import { FormGroup } from '@angular/forms'; import { DynamicDisabledModel } from './dynamic-disabled.model'; +/** + * Component representing a simple disabled input field + */ @Component({ selector: 'ds-dynamic-disabled', templateUrl: './dynamic-disabled.component.html' diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts index 05de204fa8..0fa2b3e5ed 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts @@ -7,6 +7,9 @@ export interface DsDynamicDisabledModelConfig extends DsDynamicInputModelConfig value?: any; } +/** + * This model represents the data for a disabled input field + */ export class DynamicDisabledModel extends DsDynamicInputModel { @serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DISABLED; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 3421e6c5c9..f3ed3337a9 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -33,6 +33,9 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models'; ] }) +/** + * Represents a modal where the submitter can select items to be added as a certain relationship type to the object being submitted + */ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy { label: string; relationshipOptions: RelationshipOptions; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts index dbd0938945..f32836eef1 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions.ts @@ -11,7 +11,9 @@ export const NameVariantActionTypes = { }; /* tslint:disable:max-classes-per-file */ - +/** + * Abstract class for actions that happen to name variants + */ export abstract class NameVariantListAction implements Action { type; payload: { @@ -24,6 +26,9 @@ export abstract class NameVariantListAction implements Action { } } +/** + * Action for setting a new name on an item in a certain list + */ export class SetNameVariantAction extends NameVariantListAction { type = NameVariantActionTypes.SET_NAME_VARIANT; payload: { @@ -38,6 +43,9 @@ export class SetNameVariantAction extends NameVariantListAction { } } +/** + * Action for removing a name on an item in a certain list + */ export class RemoveNameVariantAction extends NameVariantListAction { type = NameVariantActionTypes.REMOVE_NAME_VARIANT; constructor(listID: string, itemID: string) { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts index 7b530f39c5..9402ef6d19 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts @@ -72,6 +72,11 @@ export class RelationshipEffects { ) ); + /** + * Updates the namevariant in a relationship + * If the relationship is currently being added or removed, it will add the name variant to an update map so it will be sent with the next add request instead + * Otherwise the update is done immediately + */ @Effect({ dispatch: false }) updateNameVariantsActions$ = this.actions$ .pipe( ofType(RelationshipActionTypes.UPDATE_RELATIONSHIP), diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts index c7bb7104b5..9c00d64953 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts @@ -33,6 +33,9 @@ import { Context } from '../../../../../../core/shared/context.model'; ] }) +/** + * Tab for inside the lookup model that represents the items that can be used as a relationship in this submission + */ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy { @Input() relationship: RelationshipOptions; @Input() listId: string; @@ -63,6 +66,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest ) { } + /** + * Sets up the pagination and fixed query parameters + */ ngOnInit(): void { this.resetRoute(); this.routeService.setParameter('fixedFilterQuery', this.relationship.filter); @@ -90,12 +96,19 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest ); } + /** + * Method to reset the route when the window is opened to make sure no strange pagination issues appears + */ resetRoute() { this.router.navigate([], { queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }), }); } + /** + * Selects a page in the store + * @param page The page to select + */ selectPage(page: Array>) { this.selection$ .pipe(take(1)) @@ -106,6 +119,10 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest this.selectableListService.select(this.listId, page); } + /** + * Deselects a page in the store + * @param page the page to deselect + */ deselectPage(page: Array>) { this.allSelected = false; this.selection$ @@ -117,6 +134,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest this.selectableListService.deselect(this.listId, page); } + /** + * Select all items that were found using the current search query + */ selectAll() { this.allSelected = true; this.selectAllLoading = true; @@ -142,6 +162,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest ); } + /** + * Deselect all items + */ deselectAll() { this.allSelected = false; this.selection$ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts index b47207a957..8aa3dc3828 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts @@ -25,6 +25,9 @@ import { Context } from '../../../../../../core/shared/context.model'; ] }) +/** + * Tab for inside the lookup model that represents the currently selected relationships + */ export class DsDynamicLookupRelationSelectionTabComponent { @Input() label: string; @Input() listId: string; @@ -44,6 +47,9 @@ export class DsDynamicLookupRelationSelectionTabComponent { private searchConfigService: SearchConfigurationService) { } + /** + * Set up the selection and pagination on load + */ ngOnInit() { this.resetRoute(); this.selectionRD$ = this.searchConfigService.paginatedSearchOptions @@ -70,6 +76,9 @@ export class DsDynamicLookupRelationSelectionTabComponent { ) } + /** + * Method to reset the route when the window is opened to make sure no strange pagination issues appears + */ resetRoute() { this.router.navigate([], { queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }), diff --git a/src/app/shared/form/builder/models/relationship-options.model.ts b/src/app/shared/form/builder/models/relationship-options.model.ts index 7d9542794b..f1d3d0ae7a 100644 --- a/src/app/shared/form/builder/models/relationship-options.model.ts +++ b/src/app/shared/form/builder/models/relationship-options.model.ts @@ -1,5 +1,8 @@ const RELATION_METADATA_PREFIX = 'relation.' +/** + * The submission options for fields that can represent relationships + */ export class RelationshipOptions { relationshipType: string; filter: string; diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.ts index 49d31c6118..db3e4ac8b9 100644 --- a/src/app/shared/form/builder/parsers/disabled-field-parser.ts +++ b/src/app/shared/form/builder/parsers/disabled-field-parser.ts @@ -2,6 +2,9 @@ import { FieldParser } from './field-parser'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { DsDynamicDisabledModelConfig, DynamicDisabledModel } from '../ds-dynamic-form-ui/models/disabled/dynamic-disabled.model'; +/** + * Field parser for disabled fields + */ export class DisabledFieldParser extends FieldParser { public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any { diff --git a/src/app/shared/form/builder/parsers/parser-factory.ts b/src/app/shared/form/builder/parsers/parser-factory.ts index 1d3ace320f..d674007da4 100644 --- a/src/app/shared/form/builder/parsers/parser-factory.ts +++ b/src/app/shared/form/builder/parsers/parser-factory.ts @@ -27,6 +27,9 @@ const fieldParserDeps = [ PARSER_OPTIONS, ]; +/** + * Method to retrieve a field parder with its providers based on the input type + */ export class ParserFactory { public static getProvider(type: ParserType): StaticProvider { switch (type) { diff --git a/src/app/shared/form/builder/parsers/row-parser.ts b/src/app/shared/form/builder/parsers/row-parser.ts index 72737cfaa9..4938b9859e 100644 --- a/src/app/shared/form/builder/parsers/row-parser.ts +++ b/src/app/shared/form/builder/parsers/row-parser.ts @@ -27,6 +27,10 @@ export const ROW_ID_PREFIX = 'df-row-group-config-'; @Injectable({ providedIn: 'root' }) + +/** + * Parser the submission data for a single row + */ export class RowParser { constructor(private parentInjector: Injector) { } diff --git a/src/app/shared/object-list/selectable-list/selectable-list.actions.ts b/src/app/shared/object-list/selectable-list/selectable-list.actions.ts index 7b868c99ff..3dedf7e6a2 100644 --- a/src/app/shared/object-list/selectable-list/selectable-list.actions.ts +++ b/src/app/shared/object-list/selectable-list/selectable-list.actions.ts @@ -19,6 +19,9 @@ export const SelectableListActionTypes = { DESELECT_ALL: type('dspace/selectable-lists/DESELECT_ALL') }; +/** + * Abstract action class for actions on selectable lists + */ /* tslint:disable:max-classes-per-file */ export abstract class SelectableListAction implements Action { // tslint:disable-next-line:no-shadowed-variable @@ -27,7 +30,7 @@ export abstract class SelectableListAction implements Action { } /** - * Used to select an item in a the selectable list + * Action to select objects in a the selectable list */ export class SelectableListSelectAction extends SelectableListAction { payload: ListableObject[]; @@ -37,7 +40,9 @@ export class SelectableListSelectAction extends SelectableListAction { this.payload = objects; } } - +/** + * Action to select a single object in a the selectable list + */ export class SelectableListSelectSingleAction extends SelectableListAction { payload: { object: ListableObject, @@ -49,6 +54,9 @@ export class SelectableListSelectSingleAction extends SelectableListAction { } } +/** + * Action to deselect objects in a the selectable list + */ export class SelectableListDeselectSingleAction extends SelectableListAction { payload: ListableObject; @@ -58,6 +66,9 @@ export class SelectableListDeselectSingleAction extends SelectableListAction { } } +/** + * Action to deselect a single object in a the selectable list + */ export class SelectableListDeselectAction extends SelectableListAction { payload: ListableObject[]; @@ -67,6 +78,9 @@ export class SelectableListDeselectAction extends SelectableListAction { } } +/** + * Action to set a new or overwrite an existing selection + */ export class SelectableListSetSelectionAction extends SelectableListAction { payload: ListableObject[]; @@ -76,6 +90,9 @@ export class SelectableListSetSelectionAction extends SelectableListAction { } } +/** + * Action to deselect all currently selected objects + */ export class SelectableListDeselectAllAction extends SelectableListAction { constructor(id: string) { super(SelectableListActionTypes.DESELECT_ALL, id); diff --git a/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts b/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts index 927e20ff21..4c7251e563 100644 --- a/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts +++ b/src/app/shared/object-list/selectable-list/selectable-list.reducer.ts @@ -63,12 +63,22 @@ export function selectableListReducer(state: SelectableListsState = {}, action: } } +/** + * Adds multiple objects to the existing selection state + * @param state The current state + * @param action The action to perform + */ function select(state: SelectableListState, action: SelectableListSelectAction) { const filteredNewObjects = action.payload.filter((object) => !isObjectInSelection(state.selection, object)); const newSelection = [...state.selection, ...filteredNewObjects]; return Object.assign({}, state, { selection: newSelection }); } +/** + * Adds a single object to the existing selection state + * @param state The current state + * @param action The action to perform + */ function selectSingle(state: SelectableListState, action: SelectableListSelectSingleAction) { let newSelection = state.selection; if (!isObjectInSelection(state.selection, action.payload.object)) { @@ -77,11 +87,21 @@ function selectSingle(state: SelectableListState, action: SelectableListSelectSi return Object.assign({}, state, { selection: newSelection }); } +/** + * Removes multiple objects in the existing selection state + * @param state The current state + * @param action The action to perform + */ function deselect(state: SelectableListState, action: SelectableListDeselectAction) { const newSelection = state.selection.filter((selected) => hasNoValue(action.payload.find((object) => object.equals(selected)))); return Object.assign({}, state, { selection: newSelection }); } +/** Removes a single object from the existing selection state + * + * @param state The current state + * @param action The action to perform + */ function deselectSingle(state: SelectableListState, action: SelectableListDeselectSingleAction) { const newSelection = state.selection.filter((selected) => { return !selected.equals(action.payload); @@ -89,14 +109,29 @@ function deselectSingle(state: SelectableListState, action: SelectableListDesele return Object.assign({}, state, { selection: newSelection }); } +/** + * Sets the selection state of the list + * @param state The current state + * @param action The action to perform + */ function setList(state: SelectableListState, action: SelectableListSetSelectionAction) { return Object.assign({}, state, { selection: action.payload }); } +/** + * Clears the selection + * @param state The current state + * @param action The action to perform + */ function clearSelection(id: string) { return { id: id, selection: [] }; } +/** + * Checks whether the object is in currently in the selection + * @param state The current state + * @param action The action to perform + */ function isObjectInSelection(selection: ListableObject[], object: ListableObject) { return selection.findIndex((selected) => selected.equals(object)) >= 0 } From b98cec011b3cc7a40bc01c94c6e8bd4c515f003d Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 19 Dec 2019 11:15:44 +0100 Subject: [PATCH 11/12] added documentation for endpoint mock service --- src/app/app.module.ts | 4 ++-- src/app/core/core.module.ts | 8 ++++++-- .../endpoint-mocking-rest.service.spec.ts | 4 ++-- .../dspace-rest-v2/endpoint-mocking-rest.service.ts | 12 +++++++----- .../mocks}/dspace-rest-v2/mocks/mock-response-map.ts | 2 +- .../mocks/mock-submission-response.json | 0 6 files changed, 18 insertions(+), 12 deletions(-) rename src/app/{core => shared/mocks}/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts (93%) rename src/app/{core => shared/mocks}/dspace-rest-v2/endpoint-mocking-rest.service.ts (85%) rename src/app/{core => shared/mocks}/dspace-rest-v2/mocks/mock-response-map.ts (82%) rename src/app/{core => shared/mocks}/dspace-rest-v2/mocks/mock-submission-response.json (100%) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6d89d789a..d70eeede6a 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -37,8 +37,8 @@ import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.comp import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; 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 { JournalEntitiesModule } from './shared/mocks/dspace-restv2/entity-groups/journal-entities/journal-entities.module'; +import { ResearchEntitiesModule } from './shared/mocks/dspace-restv2/entity-groups/research-entities/research-entities.module'; import { ClientCookieService } from './core/services/client-cookie.service'; export function getConfig() { diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index dedf6104d9..4fdef02357 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -128,8 +128,8 @@ import { MOCK_RESPONSE_MAP, MockResponseMap, mockResponseMap -} from './dspace-rest-v2/mocks/mock-response-map'; -import { EndpointMockingRestService } from './dspace-rest-v2/endpoint-mocking-rest.service'; +} from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map'; +import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service'; import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config'; import { SearchFilterService } from './shared/search/search-filter.service'; import { SearchConfigurationService } from './shared/search/search-configuration.service'; @@ -137,6 +137,10 @@ import { SelectableListService } from '../shared/object-list/selectable-list/sel import { RelationshipTypeService } from './data/relationship-type.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; +/** + * When not in production, endpoint responses can be mocked for testing purposes + * If there is no mock version available for the endpoint, the actual REST response will be used just like in production mode + */ export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => { if (ENV_CONFIG.production) { return new DSpaceRESTv2Service(http); diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts similarity index 93% rename from src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts rename to src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts index a53762e8ce..e0dae08470 100644 --- a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts +++ b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.spec.ts @@ -1,7 +1,7 @@ import { HttpHeaders, HttpResponse } from '@angular/common/http'; import { of as observableOf } from 'rxjs'; -import { GlobalConfig } from '../../../config/global-config.interface'; -import { RestRequestMethod } from '../data/rest-request-method'; +import { GlobalConfig } from '../../../../config/global-config.interface'; +import { RestRequestMethod } from '../../../core/data/rest-request-method'; import { EndpointMockingRestService } from './endpoint-mocking-rest.service'; import { MockResponseMap } from './mocks/mock-response-map'; diff --git a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts similarity index 85% rename from src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts rename to src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts index 86ec5986c6..b0e89b80b5 100644 --- a/src/app/core/dspace-rest-v2/endpoint-mocking-rest.service.ts +++ b/src/app/shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service.ts @@ -1,12 +1,12 @@ import { HttpClient, HttpHeaders } from '@angular/common/http' import { Inject, Injectable } from '@angular/core'; import { Observable, of as observableOf } from 'rxjs'; -import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; -import { isEmpty } from '../../shared/empty.util'; -import { RestRequestMethod } from '../data/rest-request-method'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; +import { isEmpty } from '../../empty.util'; +import { RestRequestMethod } from '../../../core/data/rest-request-method'; -import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model'; -import { DSpaceRESTv2Service, HttpOptions } from './dspace-rest-v2.service'; +import { DSpaceRESTV2Response } from '../../../core/dspace-rest-v2/dspace-rest-v2-response.model'; +import { DSpaceRESTv2Service, HttpOptions } from '../../../core/dspace-rest-v2/dspace-rest-v2.service'; import { MOCK_RESPONSE_MAP, MockResponseMap } from './mocks/mock-response-map'; import * as URL from 'url-parse'; @@ -14,6 +14,8 @@ import * as URL from 'url-parse'; * Service to access DSpace's REST API. * * If a URL is found in this.mockResponseMap, it returns the mock response instead + * This service can be used for mocking REST responses when developing new features + * This is especially useful, when a REST endpoint is broken or does not exist yet */ @Injectable() export class EndpointMockingRestService extends DSpaceRESTv2Service { diff --git a/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts similarity index 82% rename from src/app/core/dspace-rest-v2/mocks/mock-response-map.ts rename to src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts index cea526b078..1d1b47ee78 100644 --- a/src/app/core/dspace-rest-v2/mocks/mock-response-map.ts +++ b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts @@ -1,5 +1,5 @@ import { InjectionToken } from '@angular/core'; -import mockSubmissionResponse from '../mocks/mock-submission-response.json'; +import mockSubmissionResponse from './mock-submission-response.json'; export class MockResponseMap extends Map {}; diff --git a/src/app/core/dspace-rest-v2/mocks/mock-submission-response.json b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-submission-response.json similarity index 100% rename from src/app/core/dspace-rest-v2/mocks/mock-submission-response.json rename to src/app/shared/mocks/dspace-rest-v2/mocks/mock-submission-response.json From 20b4da5c33903e0d25bb0ed412f3086f59cd19e9 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 19 Dec 2019 11:27:10 +0100 Subject: [PATCH 12/12] fixed import issue --- src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d70eeede6a..926575d711 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -37,9 +37,9 @@ import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.comp import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; import { NavbarModule } from './navbar/navbar.module'; -import { JournalEntitiesModule } from './shared/mocks/dspace-restv2/entity-groups/journal-entities/journal-entities.module'; -import { ResearchEntitiesModule } from './shared/mocks/dspace-restv2/entity-groups/research-entities/research-entities.module'; import { ClientCookieService } from './core/services/client-cookie.service'; +import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; +import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; export function getConfig() { return ENV_CONFIG;