From ac58297cbf1d5d5b3d912a0b2c8cd30938773b13 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 13 Nov 2019 17:23:28 +0100 Subject: [PATCH 01/60] 66156: Tabbed related entities search --- resources/i18n/en.json5 | 4 +++ src/app/+item-page/item-page.module.ts | 7 ++-- ...bed-related-entities-search.component.html | 13 +++++++ ...abbed-related-entities-search.component.ts | 36 +++++++++++++++++++ .../item-pages/journal/journal.component.html | 6 ++-- .../item-pages/person/person.component.html | 6 ++-- .../item-pages/journal/journal.component.html | 6 ++-- .../item-pages/person/person.component.html | 6 ++-- 8 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index dfcb397f4f..b088ce5545 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -405,6 +405,10 @@ "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.relationships.isAuthorOfPublication": "Publications", + "item.page.relationships.isJournalOfPublication": "Publications", + "item.page.subject": "Keywords", "item.page.uri": "URI", diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 2a5d0b6da7..b4705f1e7c 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -26,6 +26,7 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component'; import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; 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'; @NgModule({ imports: [ @@ -53,7 +54,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field ItemComponent, GenericItemPageFieldComponent, MetadataRepresentationListComponent, - RelatedEntitiesSearchComponent + RelatedEntitiesSearchComponent, + TabbedRelatedEntitiesSearchComponent ], exports: [ ItemComponent, @@ -63,7 +65,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field RelatedEntitiesSearchComponent, RelatedItemsComponent, MetadataRepresentationListComponent, - ItemPageTitleFieldComponent + ItemPageTitleFieldComponent, + TabbedRelatedEntitiesSearchComponent ], entryComponents: [ PublicationComponent diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html new file mode 100644 index 0000000000..ea142a2337 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -0,0 +1,13 @@ + + + +
+ + +
+
+
+
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts new file mode 100644 index 0000000000..c424f2d765 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from '@angular/core'; +import { Item } from '../../../../core/shared/item.model'; + +@Component({ + selector: 'ds-tabbed-related-entities-search', + templateUrl: './tabbed-related-entities-search.component.html' +}) +/** + * A component to show related items as search results, split into tabs by relationship-type + * Related items can be facetted, or queried using an + * optional search box. + */ +export class TabbedRelatedEntitiesSearchComponent { + /** + * The types of relationships to fetch items for + * e.g. 'isAuthorOfPublication' + */ + @Input() relationTypes: string[]; + + /** + * The item to render relationships for + */ + @Input() item: Item; + + /** + * Whether or not the search bar and title should be displayed (defaults to true) + * @type {boolean} + */ + @Input() searchEnabled = true; + + /** + * The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4) + * @type {number} + */ + @Input() sideBarWidth = 4; +} diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index a82d3c5df6..1f3fb2f159 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -35,8 +35,8 @@
- - + +
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 04d7b9e062..560f470ce5 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -51,8 +51,8 @@
- - + +
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index ef827af590..8d79669419 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -61,7 +61,7 @@

{{"item.page.journal.search.title" | translate}}

- - + + diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index 54d7962b97..88f0478a9a 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -79,7 +79,7 @@

{{"item.page.person.search.title" | translate}}

- - + + From aedd4b99eec216cce6fc63978fd1d1b6db0b6e40 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 14 Nov 2019 13:29:39 +0100 Subject: [PATCH 02/60] 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 03/60] 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 87d468f9b952dd1ac2b51ac45a9fda99b7f283c2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 19 Nov 2019 14:42:58 +0100 Subject: [PATCH 04/60] 66156: Hide tabbox on single tab, merged filtered- and configuration-search-page into one component and added configuration option to tabs --- .../related-entities-search.component.html | 5 +- .../related-entities-search.component.ts | 16 ++-- ...bed-related-entities-search.component.html | 15 +++- ...abbed-related-entities-search.component.ts | 6 +- .../configuration-search-page.component.ts | 12 ++- .../filtered-search-page.component.spec.ts | 21 ------ .../filtered-search-page.component.ts | 73 ------------------- src/app/+search-page/search-page.module.ts | 2 - .../item-pages/journal/journal.component.html | 5 +- .../item-pages/person/person.component.html | 5 +- .../item-pages/journal/journal.component.html | 5 +- .../item-pages/person/person.component.html | 5 +- 12 files changed, 54 insertions(+), 116 deletions(-) delete mode 100644 src/app/+search-page/filtered-search-page.component.spec.ts delete mode 100644 src/app/+search-page/filtered-search-page.component.ts diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html index c7e1f77264..75f3b7aaad 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html @@ -1,6 +1,7 @@ - - + diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts index 4c0b127925..8f65cb9858 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts @@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit { */ @Input() relationType: string; + /** + * An optional configuration to use for the search options + */ + @Input() configuration: string; + /** * The item to render relationships for */ @Input() item: Item; - /** - * The entity type of the relationship items to be displayed - * e.g. 'publication' - * This determines the title of the search results (if search is enabled) - */ - @Input() relationEntityType: string; - /** * Whether or not the search bar and title should be displayed (defaults to true) * @type {boolean} @@ -56,8 +54,8 @@ export class RelatedEntitiesSearchComponent implements OnInit { if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) { this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id); } - if (isNotEmpty(this.relationEntityType)) { - this.configuration$ = of(this.relationEntityType); + if (isNotEmpty(this.configuration)) { + this.configuration$ = of(this.configuration); } } diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index ea142a2337..69670d8f51 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,9 +1,10 @@ - - + +
@@ -11,3 +12,11 @@ +
+ + +
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index c424f2d765..3b46300267 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -15,7 +15,11 @@ export class TabbedRelatedEntitiesSearchComponent { * The types of relationships to fetch items for * e.g. 'isAuthorOfPublication' */ - @Input() relationTypes: string[]; + @Input() relationTypes: Array<{ + label: string, + filter: string, + configuration?: string + }>; /** * The item to render relationships for diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index b1a94fc086..b36353584c 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -35,6 +35,12 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem */ @Input() configuration: string; + /** + * The actual query for the fixed filter. + * If empty, the query will be determined by the route parameter called 'filter' + */ + @Input() fixedFilterQuery: string; + constructor(protected service: SearchService, protected sidebarService: SearchSidebarService, protected windowService: HostWindowService, @@ -64,7 +70,11 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem return this.searchConfigService.paginatedSearchOptions.pipe( map((options: PaginatedSearchOptions) => { const config = this.configuration || options.configuration; - return Object.assign(options, { configuration: config }); + const filter = this.fixedFilterQuery || options.fixedFilter; + return Object.assign(options, { + configuration: config, + fixedFilter: filter + }); }) ); } diff --git a/src/app/+search-page/filtered-search-page.component.spec.ts b/src/app/+search-page/filtered-search-page.component.spec.ts deleted file mode 100644 index 59ab9d7b0d..0000000000 --- a/src/app/+search-page/filtered-search-page.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FilteredSearchPageComponent } from './filtered-search-page.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { configureSearchComponentTestingModule } from './search-page.component.spec'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; - -describe('FilteredSearchPageComponent', () => { - let comp: FilteredSearchPageComponent; - let fixture: ComponentFixture; - let searchConfigService: SearchConfigurationService; - - beforeEach(async(() => { - configureSearchComponentTestingModule(FilteredSearchPageComponent); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(FilteredSearchPageComponent); - comp = fixture.componentInstance; - searchConfigService = (comp as any).searchConfigService; - fixture.detectChanges(); - }); -}); diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts deleted file mode 100644 index 0bcc9e14e3..0000000000 --- a/src/app/+search-page/filtered-search-page.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { HostWindowService } from '../shared/host-window.service'; -import { SearchService } from './search-service/search.service'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; -import { SearchPageComponent } from './search-page.component'; -import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { pushInOut } from '../shared/animations/push'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; -import { Observable } from 'rxjs'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; -import { map } from 'rxjs/operators'; -import { RouteService } from '../core/services/route.service'; - -/** - * This component renders a simple item page. - * The route parameter 'id' is used to request the item it represents. - * All fields of the item that should be displayed, are defined in its template. - */ -@Component({ - selector: 'ds-filtered-search-page', - styleUrls: ['./search-page.component.scss'], - templateUrl: './search-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [pushInOut], - providers: [ - { - provide: SEARCH_CONFIG_SERVICE, - useClass: SearchConfigurationService - } - ] -}) - -export class FilteredSearchPageComponent extends SearchPageComponent implements OnInit { - /** - * The actual query for the fixed filter. - * If empty, the query will be determined by the route parameter called 'filter' - */ - @Input() fixedFilterQuery: string; - - constructor(protected service: SearchService, - protected sidebarService: SearchSidebarService, - protected windowService: HostWindowService, - @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, - protected routeService: RouteService) { - super(service, sidebarService, windowService, searchConfigService, routeService); - } - - /** - * Listening to changes in the paginated search options - * If something changes, update the search results - * - * Listen to changes in the scope - * If something changes, update the list of scopes for the dropdown - */ - ngOnInit(): void { - super.ngOnInit(); - } - - /** - * Get the current paginated search options after updating the fixed filter using the fixedFilterQuery input - * This is to make sure the fixed filter is included in the paginated search options, as it is not part of any - * query or route parameters - * @returns {Observable} - */ - protected getSearchOptions(): Observable { - return this.searchConfigService.paginatedSearchOptions.pipe( - map((options: PaginatedSearchOptions) => { - const filter = this.fixedFilterQuery || options.fixedFilter; - return Object.assign(options, { fixedFilter: filter }); - }) - ); - } -} diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index f4c665d06e..efc647f086 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -32,7 +32,6 @@ import { SearchAuthorityFilterComponent } from './search-filters/search-filter/s import { SearchLabelComponent } from './search-labels/search-label/search-label.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; -import { FilteredSearchPageComponent } from './filtered-search-page.component'; const effects = [ SearchSidebarEffects @@ -59,7 +58,6 @@ const components = [ SearchFacetRangeOptionComponent, SearchSwitchConfigurationComponent, SearchAuthorityFilterComponent, - FilteredSearchPageComponent, ConfigurationSearchPageComponent ]; diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 562d4b44fc..e86ab35e0e 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -37,7 +37,10 @@
+ [relationTypes]="[{ + label: 'isJournalOfPublication', + filter: 'isJournalOfPublication' + }]">
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 538dd4a241..97a3cf416e 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -54,7 +54,10 @@
+ [relationTypes]="[{ + label: 'isAuthorOfPublication', + filter: 'isAuthorOfPublication' + }]">
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 2ad6cbb0ff..089511804e 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -62,6 +62,9 @@

{{"item.page.journal.search.title" | translate}}

+ [relationTypes]="[{ + label: 'isJournalOfPublication', + filter: 'isJournalOfPublication' + }]"> diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index 6f22006164..1679f9354d 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -80,6 +80,9 @@

{{"item.page.person.search.title" | translate}}

+ [relationTypes]="[{ + label: 'isAuthorOfPublication', + filter: 'isAuthorOfPublication' + }]"> From 63257eac3d766f6e734b3ae83ae8af33128c6fac Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 21 Nov 2019 12:45:38 +0100 Subject: [PATCH 05/60] 66156: AoT build error fix --- .../related-entities-search.component.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts index 711e1b9d3d..7eeddf8e70 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts @@ -16,7 +16,7 @@ describe('RelatedEntitiesSearchComponent', () => { id: 'id1' }); const mockRelationType = 'publicationsOfAuthor'; - const mockRelationEntityType = 'publication'; + const mockConfiguration = 'publication'; const mockFilter= `f.${mockRelationType}=${mockItem.id}`; const fixedFilterServiceStub = { getFilterByRelation: () => mockFilter @@ -39,7 +39,7 @@ describe('RelatedEntitiesSearchComponent', () => { fixedFilterService = (comp as any).fixedFilterService; comp.relationType = mockRelationType; comp.item = mockItem; - comp.relationEntityType = mockRelationEntityType; + comp.configuration = mockConfiguration; fixture.detectChanges(); }); @@ -49,7 +49,7 @@ describe('RelatedEntitiesSearchComponent', () => { it('should create a configuration$', () => { comp.configuration$.subscribe((configuration) => { - expect(configuration).toEqual(mockRelationEntityType); + expect(configuration).toEqual(mockConfiguration); }) }); From 64c9d009a99a133bcbcd7c5e79c48386c6ad2a7e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 21 Nov 2019 13:28:33 +0100 Subject: [PATCH 06/60] 66156: Active tab in URL for tabbed related entities search --- ...bed-related-entities-search.component.html | 4 +- ...-related-entities-search.component.spec.ts | 83 +++++++++++++++++++ ...abbed-related-entities-search.component.ts | 36 +++++++- 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index 69670d8f51..d3690c74be 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,5 +1,5 @@ - - + +
{ + let comp: TabbedRelatedEntitiesSearchComponent; + let fixture: ComponentFixture; + + const mockItem = Object.assign(new Item(), { + id: 'id1' + }); + const mockRelationType = 'publications'; + const relationTypes = [ + { + label: mockRelationType, + filter: mockRelationType + } + ]; + + const router = new MockRouter(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), NoopAnimationsModule, NgbModule.forRoot()], + declarations: [TabbedRelatedEntitiesSearchComponent, VarDirective], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: { + tab: mockRelationType + } + } + }, + }, + { provide: Router, useValue: router } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TabbedRelatedEntitiesSearchComponent); + comp = fixture.componentInstance; + comp.item = mockItem; + comp.relationTypes = relationTypes; + fixture.detectChanges(); + }); + + it('should initialize the activeTab depending on the current query parameters', () => { + expect(comp.activeTab).toEqual(mockRelationType); + }); + + describe('onTabChange', () => { + const event = { + currentId: mockRelationType, + nextId: 'nextTab' + }; + + beforeEach(() => { + comp.onTabChange(event); + }); + + it('should call router natigate with the correct arguments', () => { + expect(router.navigate).toHaveBeenCalledWith([], { + relativeTo: (comp as any).route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + }); + }); + +}); diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index 3b46300267..9fc4e7ec34 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'ds-tabbed-related-entities-search', @@ -10,7 +11,7 @@ import { Item } from '../../../../core/shared/item.model'; * Related items can be facetted, or queried using an * optional search box. */ -export class TabbedRelatedEntitiesSearchComponent { +export class TabbedRelatedEntitiesSearchComponent implements OnInit { /** * The types of relationships to fetch items for * e.g. 'isAuthorOfPublication' @@ -37,4 +38,35 @@ export class TabbedRelatedEntitiesSearchComponent { * @type {number} */ @Input() sideBarWidth = 4; + + /** + * The active tab + */ + activeTab: string; + + constructor(private route: ActivatedRoute, + private router: Router) { + } + + /** + * If the url contains a "tab" query parameter, set this tab to be the active tab + */ + ngOnInit(): void { + this.activeTab = this.route.snapshot.queryParams.tab; + } + + /** + * Add a "tab" query parameter to the URL when changing tabs + * @param event + */ + onTabChange(event) { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + } + } From b34e6363791fe44f7c5e4eea2c040a2c8f659e73 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 22 Nov 2019 18:14:02 +0100 Subject: [PATCH 07/60] Add support for locally hosted fonts --- webpack/webpack.common.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 028815d958..806c0c6f40 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -15,6 +15,9 @@ module.exports = (env) => { let copyWebpackOptions = [{ from: path.join(__dirname, '..', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'), to: path.join('assets', 'fonts') + }, { + from: path.join(__dirname, '..', 'resources', 'fonts'), + to: path.join('assets', 'fonts') }, { from: path.join(__dirname, '..', 'resources', 'images'), to: path.join('assets', 'images') @@ -24,6 +27,15 @@ module.exports = (env) => { } ]; + const themeFonts = path.join(themePath, 'resources', 'fonts'); + if(theme && fs.existsSync(themeFonts)) { + copyWebpackOptions.push({ + from: themeFonts, + to: path.join('assets', 'fonts') , + force: true, + }); + } + const themeImages = path.join(themePath, 'resources', 'images'); if(theme && fs.existsSync(themeImages)) { copyWebpackOptions.push({ @@ -161,7 +173,7 @@ module.exports = (env) => { ] }, { - test: /\.html$/, + test: /\.(html|eot|ttf|svg|woff|woff2)$/, loader: 'raw-loader' } ] From 29c968250835c51b4c1b6f0e5f600257bfcaa3cb Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 22 Nov 2019 18:14:36 +0100 Subject: [PATCH 08/60] Fix variable name --- .../item-pages/journal-volume/journal-volume.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index 99d92d2af8..907f70b941 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -4,7 +4,7 @@
- +
Date: Mon, 25 Nov 2019 14:18:21 +0100 Subject: [PATCH 09/60] 66156: Persons and Projects search tabs on OrgUnit pages + mantis fix --- resources/i18n/en.json5 | 6 ++++++ .../item-pages/org-unit/org-unit.component.html | 14 ++++++++++++++ .../org-unit.component.html} | 17 +++++++++++++++++ .../org-unit.component.scss} | 2 +- 4 files changed, 38 insertions(+), 1 deletion(-) rename themes/mantis/app/entity-groups/research-entities/item-pages/{orgunit/orgunit.component.html => org-unit/org-unit.component.html} (81%) rename themes/mantis/app/entity-groups/research-entities/item-pages/{orgunit/orgunit.component.scss => org-unit/org-unit.component.scss} (86%) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 80a7648476..da58d2010c 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -822,6 +822,10 @@ "item.page.relationships.isJournalOfPublication": "Publications", + "item.page.relationships.isOrgUnitOfPerson": "Persons", + + "item.page.relationships.isOrgUnitOfProject": "Projects", + "item.page.subject": "Keywords", "item.page.uri": "URI", @@ -1272,6 +1276,8 @@ "project.page.titleprefix": "Research Project: ", + "project.search.results.head": "Project Search Results", + "publication.listelement.badge": "Publication", diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 4d97868b58..c9227338eb 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -49,4 +49,18 @@
+
+ + +
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html similarity index 81% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 15529a1bd5..4603b8d327 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -74,3 +74,20 @@ +
+
+ + +
+
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss similarity index 86% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss index 54651aede0..4a1d2516da 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; :host { > * { From 4feba32157aefb75327d3dcc51d54361912b3b47 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 9 Oct 2019 18:06:51 +0200 Subject: [PATCH 10/60] 65528: ComCol Tree with full FlatList that gets generated at page-load --- package.json | 1 + resources/i18n/en.json5 | 6 ++ src/app/app-routing.module.ts | 1 + .../CommunityListDataSource.ts | 74 +++++++++++++++++++ .../CommunityListService.ts | 24 ++++++ .../community-list-page.component.html | 2 + .../community-list-page.component.spec.ts | 25 +++++++ .../community-list-page.component.ts | 15 ++++ .../community-list-page.module.ts | 23 ++++++ .../community-list-page.routing.module.ts | 17 +++++ .../community-list.component.html | 25 +++++++ .../community-list.component.spec.ts | 25 +++++++ .../community-list.component.ts | 37 ++++++++++ yarn.lock | 12 +++ 14 files changed, 287 insertions(+) create mode 100644 src/app/community-list-page/CommunityListDataSource.ts create mode 100644 src/app/community-list-page/CommunityListService.ts create mode 100644 src/app/community-list-page/community-list-page.component.html create mode 100644 src/app/community-list-page/community-list-page.component.spec.ts create mode 100644 src/app/community-list-page/community-list-page.component.ts create mode 100644 src/app/community-list-page/community-list-page.module.ts create mode 100644 src/app/community-list-page/community-list-page.routing.module.ts create mode 100644 src/app/community-list-page/community-list/community-list.component.html create mode 100644 src/app/community-list-page/community-list/community-list.component.spec.ts create mode 100644 src/app/community-list-page/community-list/community-list.component.ts diff --git a/package.json b/package.json index 3a54b941dd..80df678e38 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ }, "dependencies": { "@angular/animations": "^6.1.4", + "@angular/cdk": "^6.4.7", "@angular/cli": "^6.1.5", "@angular/common": "^6.1.4", "@angular/core": "^6.1.4", diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 53db8e98c0..144b3128c7 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -340,6 +340,12 @@ + "communityList.tabTitle": "DSpace - Community List", + + "communityList.title": "List of Communities", + + + "community.create.head": "Create a Community", "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5085633a5b..bd29db4ab8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -27,6 +27,7 @@ export function getAdminModulePath() { RouterModule.forRoot([ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' }, + { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' }, { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' }, diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts new file mode 100644 index 0000000000..4325d8b6cb --- /dev/null +++ b/src/app/community-list-page/CommunityListDataSource.ts @@ -0,0 +1,74 @@ +import {CommunityForList, CommunityListService} from './CommunityListService'; +import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; +import {BehaviorSubject, Observable, of} from 'rxjs'; +import {catchError, finalize, map} from 'rxjs/operators'; + +export interface CommunityFlatNode { + expandable: boolean; + name: string; + level: number; + isExpanded?: boolean; + parent?: CommunityFlatNode; + community: CommunityForList; +} + +export class CommunityListDataSource implements DataSource { + + private communityListSubject = new BehaviorSubject([]); + private loadingSubject = new BehaviorSubject(false); + + public loading$ = this.loadingSubject.asObservable(); + + constructor(private communityListService: CommunityListService) {} + + connect(collectionViewer: CollectionViewer): Observable { + return this.communityListSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.communityListSubject.complete(); + this.loadingSubject.complete(); + } + + loadCommunities() { + this.loadingSubject.next(true); + + this.communityListService.getCommunityList() + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)), + map((result: CommunityForList[]) => { + const communityFlatNodes: CommunityFlatNode[] = []; + const level = 0; + return this.transformListOfCommunities(result, level, communityFlatNodes, null); + }) + ) + .subscribe((communityFlatNode) => { + this.communityListSubject.next(communityFlatNode) + }); + }; + + transformListOfCommunities(listOfCommunities: CommunityForList[], + level: number, + communityFlatNodes: CommunityFlatNode[], + parent: CommunityFlatNode): CommunityFlatNode[] { + level++; + for (const community of listOfCommunities) { + const hasSubComs = ((!!community.subcoms && community.subcoms.length > 0)); + const communityFlatNode: CommunityFlatNode = { + expandable: hasSubComs, + name: community.name, + level: level, + isExpanded: false, + community: community, + parent: parent + } + communityFlatNodes.push(communityFlatNode); + if (hasSubComs) { + this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode); + } + } + return communityFlatNodes; + } + +} diff --git a/src/app/community-list-page/CommunityListService.ts b/src/app/community-list-page/CommunityListService.ts new file mode 100644 index 0000000000..940a79ec50 --- /dev/null +++ b/src/app/community-list-page/CommunityListService.ts @@ -0,0 +1,24 @@ +import {Injectable} from '@angular/core'; +import {Observable, of} from 'rxjs'; + +export interface CommunityForList { + name: string; + subcoms?: CommunityForList[]; + collections?: string[]; + parent?: string; +} + +@Injectable() +export class CommunityListService { + + private comList: CommunityForList[] = [ + {name: 'com1', subcoms: [{name: 'subcom1', subcoms: [{name: 'subsubcom1'}], collections: null, parent: 'com1'}], collections: ['col1', 'col2'], parent: null}, + {name: 'com2', subcoms: [{name: 'subcom2', subcoms: null, collections: null, parent: 'com2'}, {name: 'subcom3', subcoms: null, collections: null, parent: 'com2'}], collections: ['col3', 'col4'], parent: null}, + {name: 'com3', subcoms: [{name: 'subcom4', subcoms: null, collections: null, parent: 'com3'}], collections: ['col5'], parent: null}, + ]; + + public getCommunityList(): Observable { + return of(this.comList); + } + +} diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html new file mode 100644 index 0000000000..21e1b496a9 --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.html @@ -0,0 +1,2 @@ +

{{ 'communityList.title' | translate }}

+ diff --git a/src/app/community-list-page/community-list-page.component.spec.ts b/src/app/community-list-page/community-list-page.component.spec.ts new file mode 100644 index 0000000000..68a98276a2 --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommunityListPageComponent } from './community-list-page.component'; + +describe('CommunityListPageComponent', () => { + let component: CommunityListPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommunityListPageComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityListPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts new file mode 100644 index 0000000000..ea1dda1bab --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-community-list-page', + templateUrl: './community-list-page.component.html', + styleUrls: ['./community-list-page.component.css'] +}) +export class CommunityListPageComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts new file mode 100644 index 0000000000..31a7c4e51a --- /dev/null +++ b/src/app/community-list-page/community-list-page.module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import {CommunityListPageComponent} from './community-list-page.component'; +import {CommunityListPageRoutingModule} from './community-list-page.routing.module'; +import { CommunityListComponent } from './community-list/community-list.component'; +import {CdkTreeModule} from '@angular/cdk/tree'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + CommunityListPageRoutingModule, + CdkTreeModule, + ], + declarations: [ + CommunityListPageComponent, + CommunityListComponent + ] +}) +export class CommunityListPageModule { + +} diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts new file mode 100644 index 0000000000..b3b254800b --- /dev/null +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import {CdkTreeModule} from '@angular/cdk/tree'; + +import {CommunityListPageComponent} from './community-list-page.component'; +import {CommunityListService} from './CommunityListService'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: CommunityListPageComponent, pathMatch: 'full', data: { title: 'communityList.tabTitle' } } + ]), + CdkTreeModule, + ], + providers: [CommunityListService] +}) +export class CommunityListPageRoutingModule { } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html new file mode 100644 index 0000000000..1bbca95b74 --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -0,0 +1,25 @@ + + + + + +
{{node.name}}
+
+ + +
+ +
{{node.name}}
+
+
+
diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts new file mode 100644 index 0000000000..dadcc11251 --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommunityListComponent } from './community-list.component'; + +describe('CommunityListComponent', () => { + let component: CommunityListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommunityListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts new file mode 100644 index 0000000000..009af5f0fb --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -0,0 +1,37 @@ +import {Component, OnInit} from '@angular/core'; +import {CommunityListService} from '../CommunityListService'; +import {CommunityFlatNode, CommunityListDataSource} from '../CommunityListDataSource'; +import {FlatTreeControl} from '@angular/cdk/tree'; + +@Component({ + selector: 'ds-community-list', + templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.css'] +}) +export class CommunityListComponent implements OnInit { + + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.expandable + ); + + dataSource: CommunityListDataSource; + + constructor(private communityListService: CommunityListService) { } + + ngOnInit() { + this.dataSource = new CommunityListDataSource(this.communityListService); + this.dataSource.loadCommunities(); + } + + hasChild = (_: number, node: CommunityFlatNode) => node.expandable; + + shouldRender(node: CommunityFlatNode) { + const parent = node.parent; + return !parent || parent.isExpanded; + } + + numberReturn(length){ + return new Array(length); + } + +} diff --git a/yarn.lock b/yarn.lock index 69f4a072ae..d5c8d144de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,13 @@ dependencies: tslib "^1.9.0" +"@angular/cdk@^6.4.7": + version "6.4.7" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-6.4.7.tgz#1549b304dd412e82bd854cc55a7d5c6772ee0411" + integrity sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg== + dependencies: + tslib "^1.7.1" + "@angular/cli@^6.1.5": version "6.1.5" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.1.5.tgz#312c062631285ff06fd07ecde8afe22cdef5a0e1" @@ -10833,6 +10840,11 @@ tsickle@^0.32.1: source-map "^0.6.0" source-map-support "^0.5.0" +tslib@^1.7.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" From 69e8221867cf57ee85efa0c1d2c58ae8aa1cd0c1 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 10 Oct 2019 10:30:49 +0200 Subject: [PATCH 11/60] 65528: ComCol Tree with flatlist that gets generated at every (un)collapse > Only creates flatlist with expanded nodes --- .../CommunityListDataSource.ts | 22 +++++++++++++------ .../community-list.component.html | 2 +- .../community-list.component.ts | 16 +++++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts index 4325d8b6cb..db1b817f7c 100644 --- a/src/app/community-list-page/CommunityListDataSource.ts +++ b/src/app/community-list-page/CommunityListDataSource.ts @@ -1,7 +1,7 @@ import {CommunityForList, CommunityListService} from './CommunityListService'; import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; import {BehaviorSubject, Observable, of} from 'rxjs'; -import {catchError, finalize, map} from 'rxjs/operators'; +import {catchError, finalize, map, take} from 'rxjs/operators'; export interface CommunityFlatNode { expandable: boolean; @@ -30,17 +30,18 @@ export class CommunityListDataSource implements DataSource { this.loadingSubject.complete(); } - loadCommunities() { + loadCommunities(expandedNodes: CommunityFlatNode[]) { this.loadingSubject.next(true); this.communityListService.getCommunityList() .pipe( + take(1), catchError(() => of([])), finalize(() => this.loadingSubject.next(false)), map((result: CommunityForList[]) => { const communityFlatNodes: CommunityFlatNode[] = []; const level = 0; - return this.transformListOfCommunities(result, level, communityFlatNodes, null); + return this.transformListOfCommunities(result, level, communityFlatNodes, null, expandedNodes); }) ) .subscribe((communityFlatNode) => { @@ -51,21 +52,28 @@ export class CommunityListDataSource implements DataSource { transformListOfCommunities(listOfCommunities: CommunityForList[], level: number, communityFlatNodes: CommunityFlatNode[], - parent: CommunityFlatNode): CommunityFlatNode[] { + parent: CommunityFlatNode, + expandedNodes: CommunityFlatNode[]): CommunityFlatNode[] { level++; for (const community of listOfCommunities) { const hasSubComs = ((!!community.subcoms && community.subcoms.length > 0)); + let expanded = false; + if (expandedNodes != null) { + const expandedNodesFound = expandedNodes.filter((node) => (node.name === community.name)); + expanded = (expandedNodesFound.length > 0); + } + console.log(community.name + 'is expanded: ' + expanded); const communityFlatNode: CommunityFlatNode = { expandable: hasSubComs, name: community.name, level: level, - isExpanded: false, + isExpanded: expanded, community: community, parent: parent } communityFlatNodes.push(communityFlatNode); - if (hasSubComs) { - this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode); + if (hasSubComs && communityFlatNode.isExpanded) { + this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode, expandedNodes); } } return communityFlatNodes; diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 1bbca95b74..eedf20b5e3 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -14,7 +14,7 @@
diff --git a/src/app/community-list-page/community-list/community-list.component.scss b/src/app/community-list-page/community-list/community-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index d6389b6a38..714024ee56 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -6,13 +6,14 @@ import {FlatTreeControl} from '@angular/cdk/tree'; @Component({ selector: 'ds-community-list', templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.scss'] }) export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.expandable + (node) => node.level, (node) => node.isExpandable ); dataSource: CommunityListDataSource; @@ -24,7 +25,7 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(null); } - hasChild = (_: number, node: FlatNode) => node.expandable; + hasChild = (_: number, node: FlatNode) => node.isExpandable; shouldRender(node: FlatNode) { const parent = node.parent; From 3daf35e4a4ad8923a8c761e70d028b3da0bdaf26 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:37:43 +0200 Subject: [PATCH 16/60] 65528: Renaming/Moving according to feedback; style and links added; chevrons fixed --- .../CommunityListDataSource.ts | 131 ----------------- .../CommunityListService.ts | 37 ----- .../community-list-adapter.ts | 134 ++++++++++++++++++ .../community-list-datasource.ts | 33 +++++ .../community-list-page.component.html | 6 +- .../community-list-page.routing.module.ts | 4 +- .../community-list.component.html | 59 +++++--- .../community-list.component.ts | 67 ++++----- 8 files changed, 243 insertions(+), 228 deletions(-) delete mode 100644 src/app/community-list-page/CommunityListDataSource.ts delete mode 100644 src/app/community-list-page/CommunityListService.ts create mode 100644 src/app/community-list-page/community-list-adapter.ts create mode 100644 src/app/community-list-page/community-list-datasource.ts diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts deleted file mode 100644 index a3ef907e6c..0000000000 --- a/src/app/community-list-page/CommunityListDataSource.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; -import { PaginatedList } from '../core/data/paginated-list'; -import { RemoteData } from '../core/data/remote-data'; -import { hasValue, isNotEmpty } from '../shared/empty.util'; -import { CommunityListService } from './CommunityListService'; -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; -import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { - catchError, - filter, - finalize, - map, - switchMap, - take, -} from 'rxjs/operators'; -import { Community } from '../core/shared/community.model'; -import { Collection } from '../core/shared/collection.model'; - -export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection; -} - -const combineAndFlatten = (obsList: Array>) => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); - -const toFlatNode = ( - c: Community | Collection, - level: number, - isExpanded: boolean, - parent?: FlatNode -): FlatNode => ({ - isExpandable: c instanceof Community, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, -}); - -export class CommunityListDataSource implements DataSource { - - private communityList$ = new BehaviorSubject([]); - private loading$ = new BehaviorSubject(false); - - constructor(private communityListService: CommunityListService) { - } - - connect(collectionViewer: CollectionViewer): Observable { - return this.communityList$.asObservable(); - } - - disconnect(collectionViewer: CollectionViewer): void { - this.communityList$.complete(); - this.loading$.complete(); - } - - loadCommunities(expandedNodes: FlatNode[]): void { - this.loading$.next(true); - - this.communityListService.communities$ - .pipe( - take(1), - switchMap((result: Community[]) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - finalize(() => this.loading$.next(false)), - ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); - }; - - private transformListOfCommunities(listOfCommunities: Community[], - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfCommunities)) { - const obsList = listOfCommunities - .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); - - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const communityFlatNode = toFlatNode(community, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const subCommunityNodes$ = community.subcommunities.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) - ); - - obsList = [...obsList, subCommunityNodes$]; - - const collectionNodes$ = community.collections.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => - rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) - ) - ); - - obsList = [...obsList, collectionNodes$]; - } - - return combineAndFlatten(obsList); - } - -} diff --git a/src/app/community-list-page/CommunityListService.ts b/src/app/community-list-page/CommunityListService.ts deleted file mode 100644 index a00f2716f5..0000000000 --- a/src/app/community-list-page/CommunityListService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Observable, of} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import { map, take, tap } from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; - -@Injectable() -export class CommunityListService { - - communities$: Observable; - - config: PaginationComponentOptions; - sortConfig: SortOptions; - - constructor(private cds: CommunityDataService) { - this.config = new PaginationComponentOptions(); - this.config.id = 'top-level-pagination'; - this.config.pageSize = 10; - this.config.currentPage = 1; - this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList() - } - - private initTopCommunityList(): void { - this.communities$ = this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).pipe( - take(1), - map((results) => results.payload.page), - ); - } - -} diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts new file mode 100644 index 0000000000..84f427bd18 --- /dev/null +++ b/src/app/community-list-page/community-list-adapter.ts @@ -0,0 +1,134 @@ +import {Injectable} from '@angular/core'; +import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; +import {Observable, of as observableOf} from 'rxjs'; +import {CommunityDataService} from '../core/data/community-data.service'; +import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; +import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; +import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {Community} from '../core/shared/community.model'; +import {Collection} from '../core/shared/collection.model'; +import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {RemoteData} from '../core/data/remote-data'; +import {PaginatedList} from '../core/data/paginated-list'; + +export interface FlatNode { + isExpandable: boolean; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection; +} + +export const combineAndFlatten = (obsList: Array>): Observable => + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); + +export const toFlatNode = ( + c: Community | Collection, + level: number, + isExpanded: boolean, + parent?: FlatNode +): FlatNode => ({ + isExpandable: c instanceof Community, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, +}); + +@Injectable() +export class CommunityListAdapter { + + communities$: Observable; + + config: PaginationComponentOptions; + sortConfig: SortOptions; + + constructor(private cds: CommunityDataService) { + this.config = new PaginationComponentOptions(); + this.config.id = 'top-level-pagination'; + this.config.pageSize = 50; + this.config.currentPage = 1; + this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList() + } + + private initTopCommunityList(): void { + this.communities$ = this.cds.findTop({ + currentPage: this.config.currentPage, + elementsPerPage: this.config.pageSize, + sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + }).pipe( + take(1), + map((results) => results.payload.page), + ); + } + + loadCommunities(expandedNodes: FlatNode[]): Observable { + return this.communities$ + .pipe( + take(1), + switchMap((result: Community[]) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])) + ); + }; + + private transformListOfCommunities(listOfCommunities: Community[], + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfCommunities)) { + const obsList = listOfCommunities + .map((community: Community) => + this.transformCommunity(community, level, parent, expandedNodes)); + + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const communityFlatNode = toFlatNode(community, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const subCommunityNodes$ = community.subcommunities.pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) + ); + + obsList = [...obsList, subCommunityNodes$]; + + const collectionNodes$ = community.collections.pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => + rd.payload.page + .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) + ) + ); + + obsList = [...obsList, collectionNodes$]; + } + + return combineAndFlatten(obsList); + } + +} diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts new file mode 100644 index 0000000000..eab377f970 --- /dev/null +++ b/src/app/community-list-page/community-list-datasource.ts @@ -0,0 +1,33 @@ +import {CommunityListAdapter, FlatNode} from './community-list-adapter'; +import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; +import {BehaviorSubject, Observable,} from 'rxjs'; +import {finalize, take,} from 'rxjs/operators'; + +export class CommunityListDatasource implements DataSource { + + private communityList$ = new BehaviorSubject([]); + private loading$ = new BehaviorSubject(false); + + constructor(private communityListService: CommunityListAdapter) { + } + + connect(collectionViewer: CollectionViewer): Observable { + this.loadCommunities(null); + return this.communityList$.asObservable(); + } + + loadCommunities(expandedNodes: FlatNode[]) { + this.loading$.next(true); + + this.communityListService.loadCommunities(expandedNodes).pipe( + take(1), + finalize(() => this.loading$.next(false)), + ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.communityList$.complete(); + this.loading$.complete(); + } + +} diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html index 21e1b496a9..bba67a7c6d 100644 --- a/src/app/community-list-page/community-list-page.component.html +++ b/src/app/community-list-page/community-list-page.component.html @@ -1,2 +1,4 @@ -

{{ 'communityList.title' | translate }}

- +
+

{{ 'communityList.title' | translate }}

+ +
diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index b3b254800b..90cd355bc5 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import {CdkTreeModule} from '@angular/cdk/tree'; import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListService} from './CommunityListService'; +import {CommunityListAdapter} from './community-list-adapter'; @NgModule({ imports: [ @@ -12,6 +12,6 @@ import {CommunityListService} from './CommunityListService'; ]), CdkTreeModule, ], - providers: [CommunityListService] + providers: [CommunityListAdapter] }) export class CommunityListPageRoutingModule { } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 55dc738ce6..a8fdc5bc7c 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,25 +1,38 @@ - - - - -
{{node.name}}
-
- - -
- -
{{node.name}}
-
-
+ + + + +
+ + {{node.name}} + +
+
+ {{node.payload.shortDescription}} +
+
+ + +
+ +
+ + {{node.name}} + +
+
+
+ {{node.payload.shortDescription}} +
+
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 714024ee56..015d71b7a9 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,46 +1,47 @@ import {Component, OnInit} from '@angular/core'; -import {CommunityListService} from '../CommunityListService'; -import {FlatNode, CommunityListDataSource} from '../CommunityListDataSource'; +import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; +import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; @Component({ - selector: 'ds-community-list', - templateUrl: './community-list.component.html', - styleUrls: ['./community-list.component.scss'] + selector: 'ds-community-list', + templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.scss'] }) export class CommunityListComponent implements OnInit { - private expandedNodes: FlatNode[] = []; + private expandedNodes: FlatNode[] = []; - treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable - ); + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.isExpandable + ); - dataSource: CommunityListDataSource; + dataSource: CommunityListDatasource; - constructor(private communityListService: CommunityListService) { } - - ngOnInit() { - this.dataSource = new CommunityListDataSource(this.communityListService); - this.dataSource.loadCommunities(null); - } - - hasChild = (_: number, node: FlatNode) => node.isExpandable; - - shouldRender(node: FlatNode) { - const parent = node.parent; - return !parent || parent.isExpanded; - } - - toggleExpanded(node: FlatNode) { - if (node.isExpanded) { - this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); - node.isExpanded = false; - } else { - this.expandedNodes.push(node); - node.isExpanded = true; + constructor(private communityListService: CommunityListAdapter) { + } + + ngOnInit() { + this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource.loadCommunities(null); + } + + hasChild = (_: number, node: FlatNode) => node.isExpandable; + + shouldRender(node: FlatNode) { + const parent = node.parent; + return !parent || parent.isExpanded; + } + + toggleExpanded(node: FlatNode) { + if (node.isExpanded) { + this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); + node.isExpanded = false; + } else { + this.expandedNodes.push(node); + node.isExpanded = true; + } + this.dataSource.loadCommunities(this.expandedNodes); } - this.dataSource.loadCommunities(this.expandedNodes); - } } From 3997ed2c99abf4da77557d90a5d7b8ce6577e2de Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:47:15 +0200 Subject: [PATCH 17/60] 65528: ComCol routing via routing modules instead of literal --- .../community-list/community-list.component.html | 4 ++-- .../community-list/community-list.component.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index a8fdc5bc7c..373a36b0f8 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -6,7 +6,7 @@
- + {{node.name}}
@@ -26,7 +26,7 @@ aria-hidden="true">
- + {{node.name}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 015d71b7a9..187914e9e9 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,6 +2,8 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; +import {getCollectionPageRoute} from "../../+collection-page/collection-page-routing.module"; +import {getCommunityPageRoute} from "../../+community-page/community-page-routing.module"; @Component({ selector: 'ds-community-list', @@ -44,4 +46,12 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(this.expandedNodes); } + getCollectionRoute(node: FlatNode): string { + return getCollectionPageRoute(node.id); + } + + getCommunityRoute(node: FlatNode): string { + return getCommunityPageRoute(node.id); + } + } From f4686ea6cfba3fedd58fc0fcaea4f28ca0f8766d Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:47:41 +0200 Subject: [PATCH 18/60] 65528: styleLint --- .../community-list/community-list.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 187914e9e9..0e571bd408 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,8 +2,8 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; -import {getCollectionPageRoute} from "../../+collection-page/collection-page-routing.module"; -import {getCommunityPageRoute} from "../../+community-page/community-page-routing.module"; +import {getCollectionPageRoute} from '../../+collection-page/collection-page-routing.module'; +import {getCommunityPageRoute} from '../../+community-page/community-page-routing.module'; @Component({ selector: 'ds-community-list', From c99b6adb3111c5d3809f2d33a888a268405ad746 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 12:42:37 +0200 Subject: [PATCH 19/60] 65600: ComCol-Tree load more links message, not yet functional --- resources/i18n/en.json5 | 1 + .../community-list-adapter.ts | 90 +++++++++++++++---- .../community-list.component.html | 19 +++- .../community-list.component.ts | 13 ++- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 144b3128c7..7ed08ac732 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -343,6 +343,7 @@ "communityList.tabTitle": "DSpace - Community List", "communityList.title": "List of Communities", + "communityList.showMore": "Show More", diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index 84f427bd18..c9443675a7 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,10 +1,10 @@ import {Injectable} from '@angular/core'; import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of as observableOf} from 'rxjs'; +import {Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {catchError, filter, map, switchMap, take, tap} from 'rxjs/operators'; import {Community} from '../core/shared/community.model'; import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; @@ -19,6 +19,7 @@ export interface FlatNode { isExpanded?: boolean; parent?: FlatNode; payload: Community | Collection; + isShowMoreNode: boolean; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -40,12 +41,28 @@ export const toFlatNode = ( isExpanded, parent, payload: c, + isShowMoreNode: false, +}); + +export const showMoreFlatNode = ( + c: Community | Collection, + level: number, + parent?: FlatNode +): FlatNode => ({ + isExpandable: false, + name: c.name, + id: c.id, + level: level, + isExpanded: false, + parent: parent, + payload: c, + isShowMoreNode: true, }); @Injectable() export class CommunityListAdapter { - communities$: Observable; + payload$: Observable>; config: PaginationComponentOptions; sortConfig: SortOptions; @@ -53,42 +70,48 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 50; + this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() } private initTopCommunityList(): void { - this.communities$ = this.cds.findTop({ + this.payload$ = this.cds.findTop({ currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} }).pipe( take(1), - map((results) => results.payload.page), + map((results) => results.payload), ); } loadCommunities(expandedNodes: FlatNode[]): Observable { - return this.communities$ + return this.payload$ .pipe( take(1), - switchMap((result: Community[]) => { + switchMap((result: PaginatedList) => { return this.transformListOfCommunities(result, 0, null, expandedNodes); }), - catchError(() => observableOf([])) + catchError(() => observableOf([])), + tap((results) => console.log('endload', results)), ); }; - private transformListOfCommunities(listOfCommunities: Community[], + private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfCommunities)) { - const obsList = listOfCommunities + if (isNotEmpty(listOfPaginatedCommunities.page)) { + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > listOfPaginatedCommunities.elementsPerPage); + let obsList = listOfPaginatedCommunities.page .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); + this.transformCommunity(community, level, parent, expandedNodes, isNotAllCommunities)); + + if (isNotAllCommunities) { + obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; + } return combineAndFlatten(obsList); } else { @@ -96,7 +119,7 @@ export class CommunityListAdapter { } } - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[], isNotAllCommunities: boolean): Observable { let isExpanded = false; if (isNotEmpty(expandedNodes)) { isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); @@ -111,7 +134,7 @@ export class CommunityListAdapter { filter((rd: RemoteData>) => rd.hasSucceeded), take(1), switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) ); obsList = [...obsList, subCommunityNodes$]; @@ -119,16 +142,45 @@ export class CommunityListAdapter { const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), take(1), - map((rd: RemoteData>) => - rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) + tap((results) => console.log('collectionstap', results)), + map((rd: RemoteData>) => { + let nodes$ = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); + if (rd.payload.elementsPerPage < rd.payload.totalElements) { + nodes$ = [...nodes$, this.addPossibleShowMoreCollectionFlatNode(level + 1, parent)]; + } + return nodes$; + } ) ); - obsList = [...obsList, collectionNodes$]; } return combineAndFlatten(obsList); } + private addPossibleShowMoreComunityFlatNode(level: number, parent: FlatNode): Observable { + const dummyCommunity = Object.assign(new Community(), { + id: '999999', + metadata: { + 'dc.title': [ + { language: 'en_US', value: 'Test' } + ] + } + }) + return of([showMoreFlatNode(dummyCommunity, level, parent)]); + } + + private addPossibleShowMoreCollectionFlatNode(level: number, parent: FlatNode): FlatNode { + const dummyCollection = Object.assign(new Collection(), { + id: '999999', + metadata: { + 'dc.title': [ + { language: 'en_US', value: 'Test' } + ] + } + }) + return showMoreFlatNode(dummyCollection, level, parent); + } + } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 373a36b0f8..1aaf07bbcb 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,6 +1,23 @@ + + + +
+ +
+ {{ 'communityList.showMore' | translate }} +
+
+
- diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 0e571bd408..6d0ad09ab0 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -28,7 +28,13 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(null); } - hasChild = (_: number, node: FlatNode) => node.isExpandable; + hasChild(_: number, node: FlatNode) { + return node.isExpandable; + } + + isShowMore(_: number, node: FlatNode) { + return node.isShowMoreNode; + } shouldRender(node: FlatNode) { const parent = node.parent; @@ -54,4 +60,9 @@ export class CommunityListComponent implements OnInit { return getCommunityPageRoute(node.id); } + getNextPage(): void { + console.log('go to next page'); + // TODO + } + } From e950c23f40a82f9d8a01a0740fb24576faa8e337 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 13:41:05 +0200 Subject: [PATCH 20/60] 65600: Theme changes&improvements and route calculation moved to tree creation --- .../community-list-adapter.ts | 6 ++- .../community-list.component.html | 40 ++++++++++++------- .../community-list.component.ts | 16 +------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index c9443675a7..b5e5ef4ca5 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -10,6 +10,8 @@ import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; +import {getCommunityPageRoute} from "../+community-page/community-page-routing.module"; +import {getCollectionPageRoute} from "../+collection-page/collection-page-routing.module"; export interface FlatNode { isExpandable: boolean; @@ -20,6 +22,7 @@ export interface FlatNode { parent?: FlatNode; payload: Community | Collection; isShowMoreNode: boolean; + route?: string; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -42,6 +45,7 @@ export const toFlatNode = ( parent, payload: c, isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); export const showMoreFlatNode = ( @@ -70,7 +74,7 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 5; + this.config.pageSize = 10; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 1aaf07bbcb..abb3e61c59 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -18,17 +18,23 @@ - - -
- - {{node.name}} - -
-
- {{node.payload.shortDescription}} + class="example-tree-node"> +
+ +
+ + {{node.name}} + +
+
+
+
+ + {{node.payload.shortDescription}} +
@@ -38,18 +44,22 @@
- + {{node.name}}
-
- {{node.payload.shortDescription}} +
+
+ + {{node.payload.shortDescription}} +
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 6d0ad09ab0..0a9d369cdf 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,8 +2,6 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; -import {getCollectionPageRoute} from '../../+collection-page/collection-page-routing.module'; -import {getCommunityPageRoute} from '../../+community-page/community-page-routing.module'; @Component({ selector: 'ds-community-list', @@ -13,6 +11,7 @@ import {getCommunityPageRoute} from '../../+community-page/community-page-routin export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; + private Arr = Array; treeControl = new FlatTreeControl( (node) => node.level, (node) => node.isExpandable @@ -36,11 +35,6 @@ export class CommunityListComponent implements OnInit { return node.isShowMoreNode; } - shouldRender(node: FlatNode) { - const parent = node.parent; - return !parent || parent.isExpanded; - } - toggleExpanded(node: FlatNode) { if (node.isExpanded) { this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); @@ -52,14 +46,6 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(this.expandedNodes); } - getCollectionRoute(node: FlatNode): string { - return getCollectionPageRoute(node.id); - } - - getCommunityRoute(node: FlatNode): string { - return getCommunityPageRoute(node.id); - } - getNextPage(): void { console.log('go to next page'); // TODO From 1400af3a5ee7cba8e242b081669f00ea308c5796 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 14:00:21 +0200 Subject: [PATCH 21/60] 65600: Indentation description more than one line --- .../community-list/community-list.component.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index abb3e61c59..5613c09415 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -30,9 +30,10 @@
-
-
- +
+
+ {{node.payload.shortDescription}}
@@ -54,8 +55,8 @@
-
-
+
+
{{node.payload.shortDescription}} From 48d893e9757f580428ade83e368376f90ef688a4 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 15:01:51 +0200 Subject: [PATCH 22/60] 65600: Pagination on top level communities by combineFlatten list of consecutive page payloads --- .../community-list-adapter.ts | 42 ++++++++++++------- .../community-list.component.ts | 8 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index b5e5ef4ca5..d7ed317f8b 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of, of as observableOf} from 'rxjs'; +import {merge, Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; @@ -10,8 +10,8 @@ import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from "../+community-page/community-page-routing.module"; -import {getCollectionPageRoute} from "../+collection-page/collection-page-routing.module"; +import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; +import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; export interface FlatNode { isExpandable: boolean; @@ -66,7 +66,7 @@ export const showMoreFlatNode = ( @Injectable() export class CommunityListAdapter { - payload$: Observable>; + payload$: Array>>; config: PaginationComponentOptions; sortConfig: SortOptions; @@ -74,33 +74,46 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 10; + this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() } private initTopCommunityList(): void { - this.payload$ = this.cds.findTop({ + this.payload$ = [this.cds.findTop({ currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} }).pipe( take(1), map((results) => results.payload), - ); + )]; + } + + getNextPageTopCommunities(): void { + this.config.currentPage = this.config.currentPage + 1; + this.payload$ = [...this.payload$, this.cds.findTop({ + currentPage: this.config.currentPage, + elementsPerPage: this.config.pageSize, + sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + }).pipe( + take(1), + map((results) => results.payload), + )]; } loadCommunities(expandedNodes: FlatNode[]): Observable { - return this.payload$ - .pipe( + const res = this.payload$.map((payload) => { + return payload.pipe( take(1), switchMap((result: PaginatedList) => { return this.transformListOfCommunities(result, 0, null, expandedNodes); }), catchError(() => observableOf([])), - tap((results) => console.log('endload', results)), ); + }); + return combineAndFlatten(res); }; private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, @@ -108,12 +121,12 @@ export class CommunityListAdapter { parent: FlatNode, expandedNodes: FlatNode[]): Observable { if (isNotEmpty(listOfPaginatedCommunities.page)) { - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > listOfPaginatedCommunities.elementsPerPage); + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * this.config.currentPage)); let obsList = listOfPaginatedCommunities.page .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes, isNotAllCommunities)); + this.transformCommunity(community, level, parent, expandedNodes)); - if (isNotAllCommunities) { + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > this.config.currentPage) { obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; } @@ -123,7 +136,7 @@ export class CommunityListAdapter { } } - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[], isNotAllCommunities: boolean): Observable { + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { let isExpanded = false; if (isNotEmpty(expandedNodes)) { isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); @@ -146,7 +159,6 @@ export class CommunityListAdapter { const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), take(1), - tap((results) => console.log('collectionstap', results)), map((rd: RemoteData>) => { let nodes$ = rd.payload.page .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 0a9d369cdf..7c551342e8 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -19,11 +19,11 @@ export class CommunityListComponent implements OnInit { dataSource: CommunityListDatasource; - constructor(private communityListService: CommunityListAdapter) { + constructor(private communityListAdapter: CommunityListAdapter) { } ngOnInit() { - this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource = new CommunityListDatasource(this.communityListAdapter); this.dataSource.loadCommunities(null); } @@ -47,8 +47,8 @@ export class CommunityListComponent implements OnInit { } getNextPage(): void { - console.log('go to next page'); - // TODO + this.communityListAdapter.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); } } From a4bf1a64c78579d5869b2bb9db8b9d846a809912 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 12:37:23 +0200 Subject: [PATCH 23/60] 65600: Pagination for subcommunities works; not yet for collections since it needs authorisation --- .../community-list-adapter.ts | 89 ++++++++++++------- .../community-list.component.html | 8 +- .../community-list.component.ts | 29 +++++- src/app/core/data/community-data.service.ts | 16 ++++ 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index d7ed317f8b..541a5d0189 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -4,14 +4,15 @@ import {merge, Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take, tap} from 'rxjs/operators'; +import {catchError, defaultIfEmpty, filter, map, switchMap, take, tap} from 'rxjs/operators'; import {Community} from '../core/shared/community.model'; import {Collection} from '../core/shared/collection.model'; -import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {hasValue, isEmpty, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; +import {CollectionDataService} from '../core/data/collection-data.service'; export interface FlatNode { isExpandable: boolean; @@ -23,6 +24,8 @@ export interface FlatNode { payload: Community | Collection; isShowMoreNode: boolean; route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -66,37 +69,42 @@ export const showMoreFlatNode = ( @Injectable() export class CommunityListAdapter { - payload$: Array>>; + payloads$: Array>>; - config: PaginationComponentOptions; - sortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - constructor(private cds: CommunityDataService) { - this.config = new PaginationComponentOptions(); - this.config.id = 'top-level-pagination'; - this.config.pageSize = 5; - this.config.currentPage = 1; - this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); + maxSubCommunitiesPerPage: number; + + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() + + this.maxSubCommunitiesPerPage = 3; } private initTopCommunityList(): void { - this.payload$ = [this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} }).pipe( take(1), map((results) => results.payload), )]; + } getNextPageTopCommunities(): void { - this.config.currentPage = this.config.currentPage + 1; - this.payload$ = [...this.payload$, this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} }).pipe( take(1), map((results) => results.payload), @@ -104,7 +112,7 @@ export class CommunityListAdapter { } loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payload$.map((payload) => { + const res = this.payloads$.map((payload) => { return payload.pipe( take(1), switchMap((result: PaginatedList) => { @@ -121,12 +129,16 @@ export class CommunityListAdapter { parent: FlatNode, expandedNodes: FlatNode[]): Observable { if (isNotEmpty(listOfPaginatedCommunities.page)) { - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * this.config.currentPage)); + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); let obsList = listOfPaginatedCommunities.page - .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); - - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > this.config.currentPage) { + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; } @@ -147,14 +159,25 @@ export class CommunityListAdapter { let obsList = [observableOf([communityFlatNode])]; if (isExpanded) { - const subCommunityNodes$ = community.subcommunities.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); + const currentPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms$ = []; + for (let i = 1; i <= currentPage ; i++) { + const p = this.communityDataService.findSubCommunitiesPerParentCommunity(community.uuid,{elementsPerPage: this.maxSubCommunitiesPerPage, currentPage: i}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - obsList = [...obsList, subCommunityNodes$]; + ); + subcoms$ = [...subcoms$, p]; + } + + obsList = [...obsList, combineAndFlatten(subcoms$)]; + + // need to be authorized (logged in) to receive collections this way + // const cols = this.collectionDataService.getAuthorizedCollectionByCommunity(community.uuid,{elementsPerPage: 2}); + // cols.pipe(take(1)).subscribe((val) => console.log('cols:', val)); const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 5613c09415..77e9e1ee40 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -4,10 +4,10 @@
+ (click)="getNextPage(node)"> @@ -26,7 +26,7 @@
- {{node.name}} + {{node.name}} {{node.id}}
@@ -51,7 +51,7 @@
- {{node.name}} + {{node.name}} {{node.id}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 7c551342e8..c45e5098ab 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,6 +2,9 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; +import {Collection} from '../../core/shared/collection.model'; +import {Community} from '../../core/shared/community.model'; +import {isEmpty} from "../../shared/empty.util"; @Component({ selector: 'ds-community-list', @@ -42,13 +45,33 @@ export class CommunityListComponent implements OnInit { } else { this.expandedNodes.push(node); node.isExpanded = true; + if (isEmpty(node.currentCollectionPage)) { + node.currentCollectionPage = 1; + } + if (isEmpty(node.currentCommunityPage)) { + node.currentCommunityPage = 1; + } } this.dataSource.loadCommunities(this.expandedNodes); } - getNextPage(): void { - this.communityListAdapter.getNextPageTopCommunities(); - this.dataSource.loadCommunities(this.expandedNodes); + getNextPage(node: FlatNode): void { + if (node.parent != null) { + if (node.parent.isExpandable) { + if (node.payload instanceof Collection) { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; + } + if (node.payload instanceof Community) { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; + } + } + this.dataSource.loadCommunities(this.expandedNodes); + } else { + this.communityListAdapter.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); + } } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index cc55fe6869..f29c3c8833 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -23,6 +23,7 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; export class CommunityDataService extends ComColDataService { protected linkPath = 'communities'; protected topLinkPath = 'communities/search/top'; + protected subcommunitiesLinkPath = 'communities/search/subCommunities'; protected cds = this; constructor( @@ -55,4 +56,19 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } + + findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindAllOptions = {}): Observable>> { + const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); + console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); + + hrefObs.pipe( + filter((href: string) => hasValue(href)), + take(1)) + .subscribe((href: string) => { + const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + this.requestService.configure(request); + }); + + return this.rdbService.buildList(hrefObs) as Observable>>; + } } From 98c8eb558a4ad46b04d127549ae08fb2473035f7 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 12:44:56 +0200 Subject: [PATCH 24/60] 65600: Renamed FindAllOptions to FindListOptions --- .../bitstream-formats.component.ts | 6 ++--- .../core/cache/models/search-param.model.ts | 2 +- src/app/core/config/config.service.spec.ts | 4 ++-- src/app/core/config/config.service.ts | 6 ++--- .../data/bitstream-format-data.service.ts | 6 ++--- src/app/core/data/collection-data.service.ts | 12 +++++----- src/app/core/data/comcol-data.service.spec.ts | 4 ++-- src/app/core/data/comcol-data.service.ts | 4 ++-- src/app/core/data/community-data.service.ts | 6 ++--- src/app/core/data/data.service.spec.ts | 6 ++--- src/app/core/data/data.service.ts | 22 +++++++++---------- .../core/data/dspace-object-data.service.ts | 4 ++-- src/app/core/data/item-data.service.spec.ts | 5 ++--- src/app/core/data/item-data.service.ts | 6 ++--- .../core/data/metadata-schema-data.service.ts | 4 ++-- src/app/core/data/request.models.ts | 4 ++-- src/app/core/data/resource-policy.service.ts | 4 ++-- src/app/core/eperson/eperson.service.ts | 4 ++-- src/app/core/eperson/group-eperson.service.ts | 4 ++-- .../submission/workflowitem-data.service.ts | 4 ++-- .../submission/workspaceitem-data.service.ts | 4 ++-- src/app/core/tasks/tasks.service.ts | 4 ++-- .../submission-form-collection.component.ts | 4 ++-- 23 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index cb7aa1ef91..ec4003c108 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; import { map, switchMap, take } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit { * The current pagination configuration for the page used by the FindAll method * Currently simply renders all bitstream formats */ - config: FindAllOptions = Object.assign(new FindAllOptions(), { + config: FindListOptions = Object.assign(new FindListOptions(), { elementsPerPage: 20 }); @@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit { * @param event The page change event */ onPageChange(event) { - this.config = Object.assign(new FindAllOptions(), this.config, { + this.config = Object.assign(new FindListOptions(), this.config, { currentPage: event, }); this.pageConfig.currentPage = event; diff --git a/src/app/core/cache/models/search-param.model.ts b/src/app/core/cache/models/search-param.model.ts index a33bbee5e6..3881dbe8b7 100644 --- a/src/app/core/cache/models/search-param.model.ts +++ b/src/app/core/cache/models/search-param.model.ts @@ -1,6 +1,6 @@ /** - * Class representing a query parameter (query?fieldName=fieldValue) used in FindAllOptions object + * Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object */ export class SearchParam { constructor(public fieldName: string, public fieldValue: any) { diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts index 87add6b656..402ee88b81 100644 --- a/src/app/core/config/config.service.spec.ts +++ b/src/app/core/config/config.service.spec.ts @@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { ConfigService } from './config.service'; import { RequestService } from '../data/request.service'; -import { ConfigRequest, FindAllOptions } from '../data/request.models'; +import { ConfigRequest, FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; @@ -27,7 +27,7 @@ describe('ConfigService', () => { let requestService: RequestService; let halService: any; - const findOptions: FindAllOptions = new FindAllOptions(); + const findOptions: FindListOptions = new FindListOptions(); const scopeName = 'traditional'; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index 340a7a97d6..db14c4a256 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -2,7 +2,7 @@ import { merge as observableMerge, Observable, throwError as observableThrowErro import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ConfigSuccessResponse } from '../cache/response.models'; -import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models'; +import { ConfigRequest, FindListOptions, RestRequest } from '../data/request.models'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ConfigData } from './config-data'; @@ -35,7 +35,7 @@ export abstract class ConfigService { return `${endpoint}/${resourceName}`; } - protected getConfigSearchHref(endpoint, options: FindAllOptions = {}): string { + protected getConfigSearchHref(endpoint, options: FindListOptions = {}): string { let result; const args = []; @@ -93,7 +93,7 @@ export abstract class ConfigService { distinctUntilChanged()); } - public getConfigBySearch(options: FindAllOptions = {}): Observable { + public getConfigBySearch(options: FindListOptions = {}): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getConfigSearchHref(endpoint, options)), filter((href: string) => isNotEmpty(href)), diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts index bdf9b16acf..7255ed3663 100644 --- a/src/app/core/data/bitstream-format-data.service.ts +++ b/src/app/core/data/bitstream-format-data.service.ts @@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models'; +import { DeleteByIDRequest, FindListOptions, PostRequest, PutRequest } from './request.models'; import { Observable } from 'rxjs'; import { find, map, tap } from 'rxjs/operators'; import { configureRequest, getResponseFromEntry } from '../shared/operators'; @@ -54,10 +54,10 @@ export class BitstreamFormatDataService extends DataService { /** * Get the endpoint for browsing bitstream formats - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ - getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 7ec31d8970..88cc80a9a8 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { Observable } from 'rxjs/internal/Observable'; -import { FindAllOptions, GetRequest } from './request.models'; +import { FindListOptions, GetRequest } from './request.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { configureRequest } from '../shared/operators'; @@ -50,11 +50,11 @@ export class CollectionDataService extends ComColDataService { /** * Get all collections the user is authorized to submit to * - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollection(options: FindAllOptions = {}): Observable>> { + getAuthorizedCollection(options: FindListOptions = {}): Observable>> { const searchHref = 'findAuthorized'; return this.searchBy(searchHref, options).pipe( @@ -65,11 +65,11 @@ export class CollectionDataService extends ComColDataService { * Get all collections the user is authorized to submit to, by community * * @param communityId The community id - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable>> { + getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable>> { const searchHref = 'findAuthorizedByCommunity'; options.searchParams = [new SearchParam('uuid', communityId)]; @@ -85,7 +85,7 @@ export class CollectionDataService extends ComColDataService { */ hasAuthorizedCollection(): Observable { const searchHref = 'findAuthorized'; - const options = new FindAllOptions(); + const options = new FindListOptions(); options.elementsPerPage = 1; return this.searchBy(searchHref, options).pipe( diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 5cc474dff9..ffefbba6f6 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -8,7 +8,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CoreState } from '../core.reducers'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; -import { FindAllOptions, FindByIDRequest } from './request.models'; +import { FindListOptions, FindByIDRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -66,7 +66,7 @@ describe('ComColDataService', () => { const dataBuildService = {} as NormalizedObjectBuildService; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; - const options = Object.assign(new FindAllOptions(), { + const options = Object.assign(new FindListOptions(), { scopeID: scopeID }); const getRequestEntry$ = (successful: boolean) => { diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 68eb3e4880..62e2acfa87 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -6,7 +6,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; -import { FindAllOptions, FindByIDRequest } from './request.models'; +import { FindListOptions, FindByIDRequest } from './request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { getResponseFromEntry } from '../shared/operators'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -26,7 +26,7 @@ export abstract class ComColDataService extends DataS * @return { Observable } * an Observable containing the scoped URL */ - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { if (isEmpty(options.scopeID)) { return this.halService.getEndpoint(linkPath); } else { diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index f29c3c8833..1e3875edd4 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -9,7 +9,7 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions, FindAllRequest } from './request.models'; +import { FindListOptions, FindAllRequest } from './request.models'; import { RemoteData } from './remote-data'; import { hasValue } from '../../shared/empty.util'; import { Observable } from 'rxjs'; @@ -44,7 +44,7 @@ export class CommunityDataService extends ComColDataService { return this.halService.getEndpoint(this.linkPath); } - findTop(options: FindAllOptions = {}): Observable>> { + findTop(options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.topLinkPath); hrefObs.pipe( filter((href: string) => hasValue(href)), @@ -57,7 +57,7 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } - findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindAllOptions = {}): Observable>> { + findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index ab3cf0ac16..f2f10eff35 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -6,7 +6,7 @@ import { CoreState } from '../core.reducers'; import { Store } from '@ngrx/store'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable, of as observableOf } from 'rxjs'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { compare, Operation } from 'fast-json-patch'; @@ -40,7 +40,7 @@ class TestService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return observableOf(endpoint); } } @@ -53,7 +53,7 @@ class DummyChangeAnalyzer implements ChangeAnalyzer { } describe('DataService', () => { let service: TestService; - let options: FindAllOptions; + let options: FindListOptions; const requestService = {} as RequestService; const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index bbfcde2677..69b2cdfcb5 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -14,7 +14,7 @@ import { RemoteData } from './remote-data'; import { CreateRequest, DeleteByIDRequest, - FindAllOptions, + FindListOptions, FindAllRequest, FindByIDRequest, GetRequest @@ -54,17 +54,17 @@ export abstract class DataService { */ protected responseMsToLive: number; - public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable + public abstract getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable /** * Create the HREF with given options object * - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @param linkPath The link path for the object * @return {Observable} * Return an observable that emits created HREF */ - protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable { + protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable { let result: Observable; const args = []; @@ -77,11 +77,11 @@ export abstract class DataService { * Create the HREF for a specific object's search method with given options object * * @param searchMethod The search method for the object - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable} * Return an observable that emits created HREF */ - protected getSearchByHref(searchMethod: string, options: FindAllOptions = {}): Observable { + protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable { let result: Observable; const args = []; @@ -101,11 +101,11 @@ export abstract class DataService { * * @param href$ The HREF to which the query string should be appended * @param args Array with additional params to combine with query string - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable} * Return an observable that emits created HREF */ - protected buildHrefFromFindOptions(href$: Observable, args: string[], options: FindAllOptions): Observable { + protected buildHrefFromFindOptions(href$: Observable, args: string[], options: FindListOptions): Observable { if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { /* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */ @@ -127,7 +127,7 @@ export abstract class DataService { } } - findAll(options: FindAllOptions = {}): Observable>> { + findAll(options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options); hrefObs.pipe( @@ -194,11 +194,11 @@ export abstract class DataService { * Make a new FindAllRequest with given search method * * @param searchMethod The search method for the object - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable>} * Return an observable that emits response from the server */ - protected searchBy(searchMethod: string, options: FindAllOptions = {}): Observable>> { + protected searchBy(searchMethod: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getSearchByHref(searchMethod, options); diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index bb02afbcd1..002ac3cdbc 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -8,7 +8,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DataService } from './data.service'; import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; @@ -32,7 +32,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 36b8e6b3c5..44c5f48cfe 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -2,14 +2,13 @@ import { Store } from '@ngrx/store'; import { cold, getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { BrowseService } from '../browse/browse.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { CoreState } from '../core.reducers'; import { ItemDataService } from './item-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DeleteRequest, - FindAllOptions, + FindListOptions, GetRequest, MappedCollectionsRequest, PostRequest, @@ -58,7 +57,7 @@ describe('ItemDataService', () => { } as HALEndpointService; const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39'; - const options = Object.assign(new FindAllOptions(), { + const options = Object.assign(new FindListOptions(), { scopeID: scopeID, sort: { field: '', diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index e616cb8020..affcae619c 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -14,7 +14,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DeleteRequest, - FindAllOptions, + FindListOptions, MappedCollectionsRequest, PatchRequest, PostRequest, PutRequest, @@ -59,10 +59,10 @@ export class ItemDataService extends DataService { /** * Get the endpoint for browsing items * (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued') - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { let field = 'dc.date.issued'; if (options.sort && options.sort.field) { field = options.sort.field; diff --git a/src/app/core/data/metadata-schema-data.service.ts b/src/app/core/data/metadata-schema-data.service.ts index b15dd6865f..662eaa6c7c 100644 --- a/src/app/core/data/metadata-schema-data.service.ts +++ b/src/app/core/data/metadata-schema-data.service.ts @@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers'; import { DataService } from './data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { HttpClient } from '@angular/common/http'; @@ -33,7 +33,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index a7d11089df..6396d7a374 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -138,7 +138,7 @@ export class FindByIDRequest extends GetRequest { } } -export class FindAllOptions { +export class FindListOptions { scopeID?: string; elementsPerPage?: number; currentPage?: number; @@ -151,7 +151,7 @@ export class FindAllRequest extends GetRequest { constructor( uuid: string, href: string, - public body?: FindAllOptions, + public body?: FindListOptions, ) { super(uuid, href); } diff --git a/src/app/core/data/resource-policy.service.ts b/src/app/core/data/resource-policy.service.ts index 1574111232..017e5cf5ee 100644 --- a/src/app/core/data/resource-policy.service.ts +++ b/src/app/core/data/resource-policy.service.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ResourcePolicy } from '../shared/resource-policy.model'; import { RemoteData } from '../data/remote-data'; @@ -36,7 +36,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } } diff --git a/src/app/core/eperson/eperson.service.ts b/src/app/core/eperson/eperson.service.ts index 70ecf3f59e..81ae532e3b 100644 --- a/src/app/core/eperson/eperson.service.ts +++ b/src/app/core/eperson/eperson.service.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { DataService } from '../data/data.service'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -8,7 +8,7 @@ import { CacheableObject } from '../cache/object-cache.reducer'; */ export abstract class EpersonService extends DataService { - public getBrowseEndpoint(options: FindAllOptions): Observable { + public getBrowseEndpoint(options: FindListOptions): Observable { return this.halService.getEndpoint(this.linkPath); } } diff --git a/src/app/core/eperson/group-eperson.service.ts b/src/app/core/eperson/group-eperson.service.ts index 2014e6120a..c8a2a78917 100644 --- a/src/app/core/eperson/group-eperson.service.ts +++ b/src/app/core/eperson/group-eperson.service.ts @@ -7,7 +7,7 @@ import { filter, map, take } from 'rxjs/operators'; import { EpersonService } from './eperson.service'; import { RequestService } from '../data/request.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Group } from './models/group.model'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -52,7 +52,7 @@ export class GroupEpersonService extends EpersonService { */ isMemberOf(groupName: string): Observable { const searchHref = 'isMemberOf'; - const options = new FindAllOptions(); + const options = new FindListOptions(); options.searchParams = [new SearchParam('groupName', groupName)]; return this.searchBy(searchHref, options).pipe( diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 43c4aecafe..47195ed0a1 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -8,7 +8,7 @@ import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; import { WorkflowItem } from './models/workflowitem.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -35,7 +35,7 @@ export class WorkflowItemDataService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions) { + public getBrowseEndpoint(options: FindListOptions) { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 4d388ec513..3f782b74a2 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers'; import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -35,7 +35,7 @@ export class WorkspaceitemDataService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions) { + public getBrowseEndpoint(options: FindListOptions) { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index f39b144c6a..cf23bfd74b 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -4,7 +4,7 @@ import { merge as observableMerge, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; -import { DeleteRequest, FindAllOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; +import { DeleteRequest, FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; import { isNotEmpty } from '../../shared/empty.util'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { ProcessTaskResponse } from './models/process-task-response'; @@ -18,7 +18,7 @@ import { CacheableObject } from '../cache/object-cache.reducer'; */ export abstract class TasksService extends DataService { - public getBrowseEndpoint(options: FindAllOptions): Observable { + public getBrowseEndpoint(options: FindListOptions): Observable { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 79d2f2a7bc..f84764d6a4 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -36,7 +36,7 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { CollectionDataService } from '../../../core/data/collection-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; /** * An interface to represent a collection entry @@ -205,7 +205,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { map((collectionRD: RemoteData) => collectionRD.payload.name) ); - const findOptions: FindAllOptions = { + const findOptions: FindListOptions = { elementsPerPage: 1000 }; From ee140e623a64c7bebf61a0984525faa7c7217243 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 13:02:24 +0200 Subject: [PATCH 25/60] 65600: Cleanup & indentation of shortDescription --- .../community-list.component.html | 26 ++++++++++++------- .../community-list.component.ts | 3 +-- src/app/core/data/community-data.service.ts | 1 - 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 77e9e1ee40..a70de8475a 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -6,7 +6,6 @@
+
+
+ + {{node.payload.shortDescription}} +
+
+ class="example-tree-node">
-
+
{{node.payload.shortDescription}}
@@ -46,18 +54,18 @@ [attr.aria-label]="'toggle ' + node.name" (click)="toggleExpanded(node)" [ngClass]="node.isExpandable ? 'visible' : 'invisible'"> - +
- {{node.name}} {{node.id}} + {{node.name}}
-
+
{{node.payload.shortDescription}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index c45e5098ab..5cce255380 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -4,7 +4,7 @@ import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; import {Collection} from '../../core/shared/collection.model'; import {Community} from '../../core/shared/community.model'; -import {isEmpty} from "../../shared/empty.util"; +import {isEmpty} from '../../shared/empty.util'; @Component({ selector: 'ds-community-list', @@ -14,7 +14,6 @@ import {isEmpty} from "../../shared/empty.util"; export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; - private Arr = Array; treeControl = new FlatTreeControl( (node) => node.level, (node) => node.isExpandable diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 1e3875edd4..7368d30202 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -59,7 +59,6 @@ export class CommunityDataService extends ComColDataService { findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); - console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); hrefObs.pipe( filter((href: string) => hasValue(href)), From aed4db6289f8b62a16064a295f87c894da947450 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 21 Aug 2019 17:31:24 +0200 Subject: [PATCH 26/60] 64387: HAL endpoint refactoring --- src/app/core/data/comcol-data.service.ts | 6 ++++++ src/app/core/data/item-data.service.ts | 10 ++++++++++ src/app/core/shared/hal-endpoint.service.ts | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 62e2acfa87..49c5c908ef 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -6,6 +6,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; +import { PaginatedList } from './paginated-list'; +import { RemoteData } from './remote-data'; import { FindListOptions, FindByIDRequest } from './request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { getResponseFromEntry } from '../shared/operators'; @@ -57,4 +59,8 @@ export abstract class ComColDataService extends DataS return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share()); } } + + public findByParentCommunity(parentUUID: string): Observable>> { + this.halService.getEndpoint(`communities/${parentUUID}/collections`) + } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index affcae619c..b729c0fafe 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -247,4 +247,14 @@ export class ItemDataService extends DataService { map((request: RequestEntry) => request.response) ); } + + /** + * Get the endpoint for an item's bitstreams + * @param itemId + */ + public getBitstreamsEndpoint(itemId: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`)) + ); + } } diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index a93d54db64..117cc074ca 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -43,8 +43,8 @@ export class HALEndpointService { ); } - public getEndpoint(linkPath: string): Observable { - return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/')); + public getEndpoint(linkPath: string, startHref?: string): Observable { + return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/')); } /** From adfe881a817aae1a32b644848e30a7553e338b70 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 16 Oct 2019 13:12:33 +0200 Subject: [PATCH 27/60] add findByParent method to ComColDataService --- src/app/core/data/collection-data.service.ts | 8 +++++++- src/app/core/data/comcol-data.service.ts | 21 ++++++++++++++++---- src/app/core/data/community-data.service.ts | 16 +++++++++++---- src/app/core/data/data.service.ts | 8 ++++---- src/app/core/data/request.models.ts | 2 +- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 88cc80a9a8..2e1eac9e9c 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { distinctUntilChanged, filter, map, take } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -136,4 +136,10 @@ export class CollectionDataService extends ComColDataService { return this.rdbService.buildList(href$); } + protected getFindByParentHref(parentUUID: string): Observable { + return this.halService.getEndpoint('communities').pipe( + switchMap((communityEndpointHref: string) => + this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)), + ); + } } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 49c5c908ef..867ee24fc1 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,6 +1,15 @@ -import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators'; +import { + distinctUntilChanged, + filter, first, + map, + mergeMap, + share, + switchMap, + take, + tap +} from 'rxjs/operators'; import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; @@ -60,7 +69,11 @@ export abstract class ComColDataService extends DataS } } - public findByParentCommunity(parentUUID: string): Observable>> { - this.halService.getEndpoint(`communities/${parentUUID}/collections`) + protected abstract getFindByParentHref(parentUUID: string): Observable; + + public findByParent(parentUUID: string, options: FindListOptions = {}): Observable>> { + const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options); + return this.findList(href$, options); } + } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 7368d30202..d28c975122 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -1,4 +1,4 @@ -import { filter, take } from 'rxjs/operators'; +import { filter, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -9,7 +9,7 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindListOptions, FindAllRequest } from './request.models'; +import { FindListOptions, FindListRequest } from './request.models'; import { RemoteData } from './remote-data'; import { hasValue } from '../../shared/empty.util'; import { Observable } from 'rxjs'; @@ -50,7 +50,7 @@ export class CommunityDataService extends ComColDataService { filter((href: string) => hasValue(href)), take(1)) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); }); @@ -64,10 +64,18 @@ export class CommunityDataService extends ComColDataService { filter((href: string) => hasValue(href)), take(1)) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); }); return this.rdbService.buildList(hrefObs) as Observable>>; } + + protected getFindByParentHref(parentUUID: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + switchMap((communityEndpointHref: string) => + this.halService.getEndpoint('subcommunities', `${communityEndpointHref}/${parentUUID}`)) + ); + } + } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 69b2cdfcb5..2518de9a7b 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -15,7 +15,7 @@ import { CreateRequest, DeleteByIDRequest, FindListOptions, - FindAllRequest, + FindListRequest, FindByIDRequest, GetRequest } from './request.models'; @@ -133,7 +133,7 @@ export abstract class DataService { hrefObs.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; } @@ -191,7 +191,7 @@ export abstract class DataService { } /** - * Make a new FindAllRequest with given search method + * Make a new FindListRequest with given search method * * @param searchMethod The search method for the object * @param options The [[FindListOptions]] object @@ -205,7 +205,7 @@ export abstract class DataService { hrefObs.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); request.responseMsToLive = 10 * 1000; this.requestService.configure(request); }); diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 6396d7a374..ca864f99de 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -147,7 +147,7 @@ export class FindListOptions { startsWith?: string; } -export class FindAllRequest extends GetRequest { +export class FindListRequest extends GetRequest { constructor( uuid: string, href: string, From 84326ef5646322f3221e80fc02b550278c5f4c1a Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 15:00:46 +0200 Subject: [PATCH 28/60] Pagination for collections & Process feedback --- resources/i18n/en.json5 | 1 + .../community-list-adapter.ts | 225 ------- .../community-list-datasource.ts | 48 +- .../community-list-page.component.html | 4 +- .../community-list-page.component.scss | 0 .../community-list-page.component.spec.ts | 28 +- .../community-list-page.component.ts | 10 +- .../community-list-page.module.ts | 26 +- .../community-list-page.routing.module.ts | 28 +- .../community-list-service.spec.ts | 560 ++++++++++++++++++ .../community-list-service.ts | 288 +++++++++ .../community-list.component.html | 157 ++--- .../community-list.component.scss | 0 .../community-list.component.spec.ts | 313 +++++++++- .../community-list.component.ts | 134 +++-- src/app/core/data/collection-data.service.ts | 2 +- src/app/core/data/comcol-data.service.spec.ts | 7 +- src/app/core/data/community-data.service.ts | 15 - src/app/core/data/data.service.ts | 8 +- src/app/navbar/navbar.component.ts | 23 +- 20 files changed, 1420 insertions(+), 457 deletions(-) delete mode 100644 src/app/community-list-page/community-list-page.component.scss create mode 100644 src/app/community-list-page/community-list-service.spec.ts create mode 100644 src/app/community-list-page/community-list-service.ts delete mode 100644 src/app/community-list-page/community-list/community-list.component.scss diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7ed08ac732..5b3747caf6 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -343,6 +343,7 @@ "communityList.tabTitle": "DSpace - Community List", "communityList.title": "List of Communities", + "communityList.showMore": "Show More", diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index 541a5d0189..e69de29bb2 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,225 +0,0 @@ -import {Injectable} from '@angular/core'; -import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {merge, Observable, of, of as observableOf} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, defaultIfEmpty, filter, map, switchMap, take, tap} from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; -import {Collection} from '../core/shared/collection.model'; -import {hasValue, isEmpty, isNotEmpty} from '../shared/empty.util'; -import {RemoteData} from '../core/data/remote-data'; -import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; -import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; -import {CollectionDataService} from '../core/data/collection-data.service'; - -export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; -} - -export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); - -export const toFlatNode = ( - c: Community | Collection, - level: number, - isExpanded: boolean, - parent?: FlatNode -): FlatNode => ({ - isExpandable: c instanceof Community, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), -}); - -export const showMoreFlatNode = ( - c: Community | Collection, - level: number, - parent?: FlatNode -): FlatNode => ({ - isExpandable: false, - name: c.name, - id: c.id, - level: level, - isExpanded: false, - parent: parent, - payload: c, - isShowMoreNode: true, -}); - -@Injectable() -export class CommunityListAdapter { - - payloads$: Array>>; - - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; - - maxSubCommunitiesPerPage: number; - - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList() - - this.maxSubCommunitiesPerPage = 3; - } - - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} - }).pipe( - take(1), - map((results) => results.payload), - )]; - - } - - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} - }).pipe( - take(1), - map((results) => results.payload), - )]; - } - - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); - }); - return combineAndFlatten(res); - }; - - private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) - }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; - } - - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const communityFlatNode = toFlatNode(community, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms$ = []; - for (let i = 1; i <= currentPage ; i++) { - const p = this.communityDataService.findSubCommunitiesPerParentCommunity(community.uuid,{elementsPerPage: this.maxSubCommunitiesPerPage, currentPage: i}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - - ); - subcoms$ = [...subcoms$, p]; - } - - obsList = [...obsList, combineAndFlatten(subcoms$)]; - - // need to be authorized (logged in) to receive collections this way - // const cols = this.collectionDataService.getAuthorizedCollectionByCommunity(community.uuid,{elementsPerPage: 2}); - // cols.pipe(take(1)).subscribe((val) => console.log('cols:', val)); - - const collectionNodes$ = community.collections.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => { - let nodes$ = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); - if (rd.payload.elementsPerPage < rd.payload.totalElements) { - nodes$ = [...nodes$, this.addPossibleShowMoreCollectionFlatNode(level + 1, parent)]; - } - return nodes$; - } - ) - ); - obsList = [...obsList, collectionNodes$]; - } - - return combineAndFlatten(obsList); - } - - private addPossibleShowMoreComunityFlatNode(level: number, parent: FlatNode): Observable { - const dummyCommunity = Object.assign(new Community(), { - id: '999999', - metadata: { - 'dc.title': [ - { language: 'en_US', value: 'Test' } - ] - } - }) - return of([showMoreFlatNode(dummyCommunity, level, parent)]); - } - - private addPossibleShowMoreCollectionFlatNode(level: number, parent: FlatNode): FlatNode { - const dummyCollection = Object.assign(new Collection(), { - id: '999999', - metadata: { - 'dc.title': [ - { language: 'en_US', value: 'Test' } - ] - } - }) - return showMoreFlatNode(dummyCollection, level, parent); - } - -} diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts index eab377f970..da4bcc00cd 100644 --- a/src/app/community-list-page/community-list-datasource.ts +++ b/src/app/community-list-page/community-list-datasource.ts @@ -1,33 +1,35 @@ -import {CommunityListAdapter, FlatNode} from './community-list-adapter'; -import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; -import {BehaviorSubject, Observable,} from 'rxjs'; -import {finalize, take,} from 'rxjs/operators'; +import { CommunityListService, FlatNode } from './community-list-service'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { BehaviorSubject, Observable, } from 'rxjs'; +import { finalize, take, } from 'rxjs/operators'; export class CommunityListDatasource implements DataSource { - private communityList$ = new BehaviorSubject([]); - private loading$ = new BehaviorSubject(false); + private communityList$ = new BehaviorSubject([]); + public loading$ = new BehaviorSubject(false); - constructor(private communityListService: CommunityListAdapter) { - } + constructor(private communityListService: CommunityListService) { + } - connect(collectionViewer: CollectionViewer): Observable { - this.loadCommunities(null); - return this.communityList$.asObservable(); - } + connect(collectionViewer: CollectionViewer): Observable { + this.loadCommunities(null); + return this.communityList$.asObservable(); + } - loadCommunities(expandedNodes: FlatNode[]) { - this.loading$.next(true); + loadCommunities(expandedNodes: FlatNode[]) { + this.loading$.next(true); - this.communityListService.loadCommunities(expandedNodes).pipe( - take(1), - finalize(() => this.loading$.next(false)), - ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); - } + this.communityListService.loadCommunities(expandedNodes).pipe( + take(1), + finalize(() => this.loading$.next(false)), + ).subscribe((flatNodes: FlatNode[]) => { + this.communityList$.next(flatNodes); + }); + } - disconnect(collectionViewer: CollectionViewer): void { - this.communityList$.complete(); - this.loading$.complete(); - } + disconnect(collectionViewer: CollectionViewer): void { + this.communityList$.complete(); + this.loading$.complete(); + } } diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html index bba67a7c6d..08accdc0e5 100644 --- a/src/app/community-list-page/community-list-page.component.html +++ b/src/app/community-list-page/community-list-page.component.html @@ -1,4 +1,4 @@
-

{{ 'communityList.title' | translate }}

- +

{{ 'communityList.title' | translate }}

+
diff --git a/src/app/community-list-page/community-list-page.component.scss b/src/app/community-list-page/community-list-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/community-list-page/community-list-page.component.spec.ts b/src/app/community-list-page/community-list-page.component.spec.ts index 68a98276a2..0aa4afce7f 100644 --- a/src/app/community-list-page/community-list-page.component.spec.ts +++ b/src/app/community-list-page/community-list-page.component.spec.ts @@ -1,6 +1,9 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { CommunityListPageComponent } from './community-list-page.component'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; describe('CommunityListPageComponent', () => { let component: CommunityListPageComponent; @@ -8,9 +11,21 @@ describe('CommunityListPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CommunityListPageComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + }, + }), + ], + declarations: [CommunityListPageComponent], + providers: [ + CommunityListPageComponent, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { @@ -19,7 +34,8 @@ describe('CommunityListPageComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + it('should create', inject([CommunityListPageComponent], (comp: CommunityListPageComponent) => { + expect(comp).toBeTruthy(); + })); + }); diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts index 76fc96dcbe..9ba715c20a 100644 --- a/src/app/community-list-page/community-list-page.component.ts +++ b/src/app/community-list-page/community-list-page.component.ts @@ -1,15 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'ds-community-list-page', templateUrl: './community-list-page.component.html', - styleUrls: ['./community-list-page.component.scss'] }) -export class CommunityListPageComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } +export class CommunityListPageComponent { } diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 31a7c4e51a..817e492bf7 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -1,22 +1,22 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; -import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListPageRoutingModule} from './community-list-page.routing.module'; +import { CommunityListPageComponent } from './community-list-page.component'; +import { CommunityListPageRoutingModule } from './community-list-page.routing.module'; import { CommunityListComponent } from './community-list/community-list.component'; -import {CdkTreeModule} from '@angular/cdk/tree'; +import { CdkTreeModule } from '@angular/cdk/tree'; @NgModule({ - imports: [ - CommonModule, - SharedModule, - CommunityListPageRoutingModule, - CdkTreeModule, - ], - declarations: [ - CommunityListPageComponent, - CommunityListComponent - ] + imports: [ + CommonModule, + SharedModule, + CommunityListPageRoutingModule, + CdkTreeModule, + ], + declarations: [ + CommunityListPageComponent, + CommunityListComponent + ] }) export class CommunityListPageModule { diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index 90cd355bc5..b226236af1 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -1,17 +1,23 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import {CdkTreeModule} from '@angular/cdk/tree'; +import { CdkTreeModule } from '@angular/cdk/tree'; -import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListAdapter} from './community-list-adapter'; +import { CommunityListPageComponent } from './community-list-page.component'; +import { CommunityListService } from './community-list-service'; @NgModule({ - imports: [ - RouterModule.forChild([ - { path: '', component: CommunityListPageComponent, pathMatch: 'full', data: { title: 'communityList.tabTitle' } } - ]), - CdkTreeModule, - ], - providers: [CommunityListAdapter] + imports: [ + RouterModule.forChild([ + { + path: '', + component: CommunityListPageComponent, + pathMatch: 'full', + data: { title: 'communityList.tabTitle' } + } + ]), + CdkTreeModule, + ], + providers: [CommunityListService] }) -export class CommunityListPageRoutingModule { } +export class CommunityListPageRoutingModule { +} diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts new file mode 100644 index 0000000000..c3cb24cc59 --- /dev/null +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -0,0 +1,560 @@ +import { TestBed, inject, async, fakeAsync } from '@angular/core/testing'; +import { CommunityListService, FlatNode, toFlatNode } from './community-list-service'; +import { CollectionDataService } from '../core/data/collection-data.service'; +import { PaginatedList } from '../core/data/paginated-list'; +import { PageInfo } from '../core/shared/page-info.model'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { + createFailedRemoteDataObject$, + createSuccessfulRemoteDataObject$ +} from '../shared/testing/utils'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { map, take } from 'rxjs/operators'; +import { FindListOptions } from '../core/data/request.models'; + +describe('CommunityListService', () => { + const standardElementsPerPage = 2; + let collectionDataServiceStub: any; + let communityDataServiceStub: any; + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + }), + Object.assign(new Community(), { + id: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + }) + ]; + const mockCollectionsPage1 = [ + Object.assign(new Collection(), { + id: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + name: 'Collection 1' + }), + Object.assign(new Collection(), { + id: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + name: 'Collection 2' + }) + ]; + const mockCollectionsPage2 = [ + Object.assign(new Collection(), { + id: 'a5159760-f362-4659-9e81-e3253ad91ede', + uuid: 'a5159760-f362-4659-9e81-e3253ad91ede', + name: 'Collection 3' + }), + Object.assign(new Collection(), { + id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + name: 'Collection 4' + }) + ]; + const mockListOfTopCommunitiesPage1 = [ + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + }), + Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + ]; + const mockListOfTopCommunitiesPage2 = [ + Object.assign(new Community(), { + id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + ]; + const mockTopCommunitiesWithChildrenArraysPage1 = [ + { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: mockSubcommunities1Page1, + collections: [], + }, + { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: [], + collections: [...mockCollectionsPage1, ...mockCollectionsPage2], + }, + { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: [], + collections: [], + }]; + const mockTopCommunitiesWithChildrenArraysPage2 = [ + { + id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + subcommunities: [], + collections: [], + }]; + + const allCommunities = [...mockTopCommunitiesWithChildrenArraysPage1, ...mockTopCommunitiesWithChildrenArraysPage2, ...mockSubcommunities1Page1]; + + let service: CommunityListService; + + beforeEach(async(() => { + communityDataServiceStub = { + findTop(options: FindListOptions = {}) { + const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2] + let currentPage = options.currentPage; + const elementsPerPage = 3; + if (currentPage === undefined) { + currentPage = 1 + } + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > allTopComs.length) { + endPageIndex = allTopComs.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allTopComs.slice(startPageIndex, endPageIndex))); + }, + findByParent(parentUUID: string, options: FindListOptions = {}) { + const foundCom = allCommunities.find((community) => (community.id === parentUUID)); + let currentPage = options.currentPage; + let elementsPerPage = options.elementsPerPage; + if (currentPage === undefined) { + currentPage = 1 + } + if (elementsPerPage === 0) { + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.subcommunities as [Community]))); + } + elementsPerPage = standardElementsPerPage; + if (foundCom !== undefined && foundCom.subcommunities !== undefined) { + const coms = foundCom.subcommunities as [Community]; + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > coms.length) { + endPageIndex = coms.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), coms.slice(startPageIndex, endPageIndex))); + } else { + return createFailedRemoteDataObject$(); + } + } + }; + collectionDataServiceStub = { + findByParent(parentUUID: string, options: FindListOptions = {}) { + const foundCom = allCommunities.find((community) => (community.id === parentUUID)); + let currentPage = options.currentPage; + let elementsPerPage = options.elementsPerPage; + if (currentPage === undefined) { + currentPage = 1 + } + if (elementsPerPage === 0) { + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.collections as [Collection]))); + } + elementsPerPage = standardElementsPerPage; + if (foundCom !== undefined && foundCom.collections !== undefined) { + const colls = foundCom.collections as [Collection]; + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > colls.length) { + endPageIndex = colls.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), colls.slice(startPageIndex, endPageIndex))); + } else { + return createFailedRemoteDataObject$(); + } + } + }; + TestBed.configureTestingModule({ + providers: [CommunityListService, + { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: CommunityDataService, useValue: communityDataServiceStub },], + }); + service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub); + })); + + afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub)); + + it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => { + expect(serviceIn).toBeTruthy(); + })); + + describe('getNextPageTopCommunities', () => { + describe('also load in second page of top communities', () => { + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the test top communities page 1 and 2', () => { + let findTopSpy; + beforeEach(() => { + findTopSpy = spyOn(communityDataServiceStub, 'findTop').and.callThrough(); + service.getNextPageTopCommunities(); + + const sub = service.loadCommunities(null) + .subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('flatnode list should contain just flatnodes of top community list page 1 and 2', () => { + expect(findTopSpy).toHaveBeenCalled(); + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockListOfTopCommunitiesPage2.length); + mockListOfTopCommunitiesPage1.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + mockListOfTopCommunitiesPage2.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + describe('loadCommunities', () => { + describe('should transform all communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => { + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the test top communities', () => { + beforeEach(() => { + const sub = service.loadCommunities(null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length); + }); + it('flatnode list should contain flatNode representations of top communities', () => { + mockListOfTopCommunitiesPage1.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + it('none of the flatnodes in the list should be expanded', () => { + flatNodeList.map((flatnode: FlatNode) => { + expect(flatnode.isExpanded).toEqual(false); + }); + }); + }); + describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const expandedNodes = []; + mockListOfTopCommunitiesPage1.map((community: Community) => { + const communityFlatNode = toFlatNode(community, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + expandedNodes.push(communityFlatNode); + }); + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of its possible page-limited children', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + describe('Just first top comm expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of page-limited children of first top community', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + }); + }); + describe('Just second top comm expanded, collections at page 2: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], true, 0, true, null); + communityFlatNode.currentCollectionPage = 2; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of page-limited children of second top community', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockCollectionsPage1.length + mockCollectionsPage2.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => { + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + mockCollectionsPage2.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + describe('transformListOfCommunities', () => { + describe('should transform list of communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => { + describe('list of communities with possible children', () => { + const listOfCommunities = mockListOfTopCommunitiesPage1; + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the communities in the test list', () => { + beforeEach(() => { + const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as community test list', () => { + expect(flatNodeList.length).toEqual(listOfCommunities.length); + }); + it('flatnode list should contain flatNode representations of all communities from test list', () => { + listOfCommunities.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + it('none of the flatnodes in the list should be expanded', () => { + flatNodeList.map((flatnode: FlatNode) => { + expect(flatnode.isExpanded).toEqual(false); + }); + }); + }); + describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const expandedNodes = []; + listOfCommunities.map((community: Community) => { + const communityFlatNode = toFlatNode(community, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + expandedNodes.push(communityFlatNode); + }); + const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as community test list and size of its possible children', () => { + expect(flatNodeList.length).toEqual(listOfCommunities.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all children', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + mockSubcommunities1Page1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + }); + + describe('transformCommunity', () => { + describe('should transform community in list of flatnodes with possible subcoms and collections as subflatnodes if its expanded', () => { + describe('topcommunity without subcoms or collections, unexpanded', () => { + const communityWithNoSubcomsOrColls = Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 2' }] + } + }); + let flatNodeList; + describe('should return list containing only flatnode corresponding to that community', () => { + beforeEach(() => { + const sub = service.transformCommunity(communityWithNoSubcomsOrColls, 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be 1', () => { + expect(flatNodeList.length).toEqual(1); + }); + it('flatnode list only element should be flatNode of test community', () => { + expect(flatNodeList[0].id).toEqual(communityWithNoSubcomsOrColls.id); + }); + it('flatnode from test community is not expanded', () => { + expect(flatNodeList[0].isExpanded).toEqual(false); + }); + }); + }); + describe('topcommunity with subcoms or collections, unexpanded', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + describe('should return list containing only flatnode corresponding to that community', () => { + beforeAll(() => { + const sub = service.transformCommunity(communityWithSubcoms, 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be 1', () => { + expect(flatNodeList.length).toEqual(1); + }); + it('flatnode list only element should be flatNode of test community', () => { + expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id); + }); + it('flatnode from test community is not expanded', () => { + expect(flatNodeList[0].isExpanded).toEqual(false); + }); + }); + }); + describe('topcommunity with subcoms, expanded, first page for all', () => { + describe('should return list containing flatnodes of that community, its possible subcommunities and its possible collections', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + beforeEach(() => { + const communityFlatNode = toFlatNode(communityWithSubcoms, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.transformCommunity(communityWithSubcoms, 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('list of flatnodes is length is 1 + nrOfSubcoms & first flatnode is of expanded test community', () => { + expect(flatNodeList.length).toEqual(1 + mockSubcommunities1Page1.length); + expect(flatNodeList[0].isExpanded).toEqual(true); + expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id); + }); + it('list of flatnodes contains flatnodes for all subcoms of test community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + }); + it('the subcoms of the test community are a level higher than the parent community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).level).toEqual(flatNodeList[0].level + 1); + }); + }); + }); + }); + describe('topcommunity with collections, expanded, on second page of collections', () => { + describe('should return list containing flatnodes of that community, its collections of the first two pages', () => { + const communityWithCollections = Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + beforeEach(() => { + const communityFlatNode = toFlatNode(communityWithCollections, true, 0, true, null); + communityFlatNode.currentCollectionPage = 2; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.transformCommunity(communityWithCollections, 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('list of flatnodes is length is 1 + nrOfCollections & first flatnode is of expanded test community', () => { + expect(flatNodeList.length).toEqual(1 + mockCollectionsPage1.length + mockCollectionsPage2.length); + expect(flatNodeList[0].isExpanded).toEqual(true); + expect(flatNodeList[0].id).toEqual(communityWithCollections.id); + }); + it('list of flatnodes contains flatnodes for all subcolls (first 2 pages) of test community', () => { + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + mockCollectionsPage2.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }) + }); + it('the collections of the test community are a level higher than the parent community', () => { + mockCollectionsPage1.map((collection) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1); + }); + mockCollectionsPage2.map((collection) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1); + }) + }); + }); + }); + }); + + }); + + describe('getIsExpandable', () => { + describe('should return true', () => { + it('if community has subcommunities', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + expect(service.getIsExpandable(communityWithSubcoms)).toEqual(true); + }); + it('if community has collections', () => { + const communityWithCollections = Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockCollectionsPage1)), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 2' }] + } + }); + expect(service.getIsExpandable(communityWithCollections)).toEqual(true); + }); + }); + describe('should return false', () => { + it('if community has neither subcommunities nor collections', () => { + const communityWithNoSubcomsOrColls = Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 3' }] + } + }); + expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(false); + }); + }); + + }); + +}); diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts new file mode 100644 index 0000000000..6da1bff4e5 --- /dev/null +++ b/src/app/community-list-page/community-list-service.ts @@ -0,0 +1,288 @@ +import { Injectable } from '@angular/core'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { Observable, of as observableOf } from 'rxjs'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { PaginatedList } from '../core/data/paginated-list'; +import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; +import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; +import { CollectionDataService } from '../core/data/collection-data.service'; + +export interface FlatNode { + isExpandable: boolean; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; +} + +export class ShowMoreFlatNode { +} + +export const combineAndFlatten = (obsList: Array>): Observable => + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); + +export const toFlatNode = ( + c: Community | Collection, + isExpandable: boolean, + level: number, + isExpanded: boolean, + parent?: FlatNode +): FlatNode => ({ + isExpandable: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), +}); + +export const showMoreFlatNode = ( + id: string, + level: number, + parent: FlatNode +): FlatNode => ({ + isExpandable: false, + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, +}); + +// tslint:disable-next-line:max-classes-per-file +@Injectable() +export class CommunityListService { + + // page-limited list of top-level communities + payloads$: Array>>; + + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; + + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; + + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); + + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } + + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Gets all top communities, limited by page, and transforms this in a list of flatnodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); + }); + return combineAndFlatten(res); + }; + + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } + + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const isExpandable = this.getIsExpandable(community); + + const communityFlatNode = toFlatNode(community, isExpandable, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, false, level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * If it does, it is expandable => returns true, false if not + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): boolean { + let isExpandable = true; + let nrOfSubcoms; + let nrOfColl; + this.communityDataService.findByParent(community.uuid, { elementsPerPage: 0 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + ) + .subscribe((result) => { + if (result.payload.totalElements === 0) { + nrOfSubcoms = result.payload.totalElements; + } + ; + }); + + this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 0 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + ) + .subscribe((result) => { + if (result.payload.totalElements === 0) { + nrOfColl = result.payload.totalElements; + } + ; + }); + if (nrOfSubcoms === 0 && nrOfColl === 0) { + isExpandable = false; + } + return isExpandable; + } + +} diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index a70de8475a..fdec67d67c 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,74 +1,91 @@ - + - - - +
+
+
+
+
+ + +
+ +
+ + {{node.name}} + +
+
+ +
+
+ + + {{node.payload.shortDescription}} + +
+
+
+
+ + +
+
+ + +
+ -
- {{ 'communityList.showMore' | translate }} -
+ +
+ + {{node.name}} + +
+
+ +
+
+ + + {{node.payload.shortDescription}} +
-
-
- - {{node.payload.shortDescription}} -
-
- - - -
- -
- - {{node.name}} - -
-
-
-
- - {{node.payload.shortDescription}} -
-
-
- - -
- -
- - {{node.name}} - -
-
-
-
- - {{node.payload.shortDescription}} -
-
-
+
+
+
diff --git a/src/app/community-list-page/community-list/community-list.component.scss b/src/app/community-list-page/community-list/community-list.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index dadcc11251..c1c9285491 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -1,16 +1,203 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { CommunityListComponent } from './community-list.component'; +import { + CommunityListService, + FlatNode, + showMoreFlatNode, + toFlatNode +} from '../community-list-service'; +import { CdkTreeModule } from '@angular/cdk/tree'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Community } from '../../core/shared/community.model'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { Collection } from '../../core/shared/collection.model'; +import { Observable, of as observableOf } from 'rxjs'; +import { By } from '@angular/platform-browser'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; describe('CommunityListComponent', () => { let component: CommunityListComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CommunityListComponent ] + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + name: 'subcommunity1', + }), + Object.assign(new Community(), { + id: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + name: 'subcommunity2', }) - .compileComponents(); + ]; + const mockCollectionsPage1 = [ + Object.assign(new Collection(), { + id: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + name: 'collection1', + }), + Object.assign(new Collection(), { + id: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + name: 'collection2', + }) + ]; + const mockCollectionsPage2 = [ + Object.assign(new Collection(), { + id: 'a5159760-f362-4659-9e81-e3253ad91ede', + uuid: 'a5159760-f362-4659-9e81-e3253ad91ede', + name: 'collection3', + }), + Object.assign(new Collection(), { + id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + name: 'collection4', + }) + ]; + + const mockTopCommunitiesWithChildrenArrays = [ + { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: mockSubcommunities1Page1, + collections: [], + }, + { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: [], + collections: [...mockCollectionsPage1, ...mockCollectionsPage2], + }, + { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: [], + collections: [], + }]; + + const mockTopFlatnodesUnexpanded: FlatNode[] = [ + toFlatNode( + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community1', + }), true, 0, false, null + ), + toFlatNode( + Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + name: 'community2', + }), true, 0, false, null + ), + toFlatNode( + Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community3', + }), false, 0, false, null + ), + ]; + let communityListServiceStub; + + beforeEach(async(() => { + communityListServiceStub = { + topPageSize: 2, + topCurrentPage: 1, + collectionPageSize: 2, + subcommunityPageSize: 2, + getNextPageTopCommunities() { + this.topCurrentPage++; + }, + loadCommunities(expandedNodes) { + let flatnodes; + let showMoreTopComNode = false; + flatnodes = [...mockTopFlatnodesUnexpanded]; + const currentPage = this.topCurrentPage; + const elementsPerPage = this.topPageSize; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex >= flatnodes.length) { + endPageIndex = flatnodes.length; + } else { + showMoreTopComNode = true; + } + if (expandedNodes === null || isEmpty(expandedNodes)) { + if (showMoreTopComNode) { + return observableOf([...mockTopFlatnodesUnexpanded.slice(0, endPageIndex), showMoreFlatNode('community', 0, null)]); + } else { + return observableOf(mockTopFlatnodesUnexpanded.slice(0, endPageIndex)); + } + } else { + flatnodes = []; + const topFlatnodes = mockTopFlatnodesUnexpanded.slice(0, endPageIndex); + topFlatnodes.map((topNode: FlatNode) => { + flatnodes = [...flatnodes, topNode] + const expandedParent: FlatNode = expandedNodes.find((expandedNode: FlatNode) => expandedNode.id === topNode.id); + if (isNotEmpty(expandedParent)) { + const matchingTopComWithArrays = mockTopCommunitiesWithChildrenArrays.find((topcom) => topcom.id === topNode.id); + if (isNotEmpty(matchingTopComWithArrays)) { + const possibleSubcoms: Community[] = matchingTopComWithArrays.subcommunities; + let subComFlatnodes = []; + possibleSubcoms.map((subcom: Community) => { + subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, false, topNode.level + 1, false, topNode)]; + }); + const possibleColls: Collection[] = matchingTopComWithArrays.collections; + let collFlatnodes = []; + possibleColls.map((coll: Collection) => { + collFlatnodes = [...collFlatnodes, toFlatNode(coll, false, topNode.level + 1, false, topNode)]; + }); + if (isNotEmpty(subComFlatnodes)) { + const endSubComIndex = this.subcommunityPageSize * expandedParent.currentCommunityPage; + flatnodes = [...flatnodes, ...subComFlatnodes.slice(0, endSubComIndex)]; + if (subComFlatnodes.length > endSubComIndex) { + flatnodes = [...flatnodes, showMoreFlatNode('community', topNode.level + 1, expandedParent)]; + } + } + if (isNotEmpty(collFlatnodes)) { + const endColIndex = this.collectionPageSize * expandedParent.currentCollectionPage; + flatnodes = [...flatnodes, ...collFlatnodes.slice(0, endColIndex)]; + if (collFlatnodes.length > endColIndex) { + flatnodes = [...flatnodes, showMoreFlatNode('collection', topNode.level + 1, expandedParent)]; + } + } + } + } + }); + if (showMoreTopComNode) { + flatnodes = [...flatnodes, showMoreFlatNode('community', 0, null)]; + } + return observableOf(flatnodes); + } + } + }; + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + }, + }), + CdkTreeModule, + RouterTestingModule], + declarations: [CommunityListComponent], + providers: [CommunityListComponent, + { provide: CommunityListService, useValue: communityListServiceStub },], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }) + .compileComponents(); })); beforeEach(() => { @@ -19,7 +206,119 @@ describe('CommunityListComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should create', inject([CommunityListComponent], (comp: CommunityListComponent) => { + expect(comp).toBeTruthy(); + })); + + it('should render a cdk tree with the first elementsPerPage (2) nr of top level communities, unexpanded', () => { + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + const allNodes = [...expandableNodesFound, ...childlessNodesFound]; + expect(allNodes.length).toEqual(2); + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); }); + + it('show more node is present at end of nodetree', () => { + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(1); + expect(showMoreEl).toBeTruthy(); + }); + + describe('when show more of top communities is clicked', () => { + beforeEach(fakeAsync(() => { + const showMoreLink = fixture.debugElement.query(By.css('.show-more-node a')); + showMoreLink.triggerEventHandler('click', { + preventDefault: () => {/**/ + } + }); + tick(); + fixture.detectChanges(); + })); + it('tree contains maximum of currentPage (2) * (2) elementsPerPage of first top communities, or less if there are less communities (3)', () => { + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + const allNodes = [...expandableNodesFound, ...childlessNodesFound]; + expect(allNodes.length).toEqual(3); + mockTopFlatnodesUnexpanded.map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + }); + it('show more node is gone from end of nodetree', () => { + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(0); + }); + }); + + describe('when first expandable node is expanded', () => { + let allNodes; + beforeEach(fakeAsync(() => { + const chevronExpand = fixture.debugElement.query(By.css('.expandable-node button')); + const chevronExpandSpan = fixture.debugElement.query(By.css('.expandable-node button span')); + if (chevronExpandSpan.nativeElement.classList.contains('fa-chevron-right')) { + chevronExpand.nativeElement.click(); + tick(); + fixture.detectChanges(); + } + + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + allNodes = [...expandableNodesFound, ...childlessNodesFound]; + })); + describe('children of first expandable node are added to tree (page-limited)', () => { + it('tree contains page-limited topcoms (2) and children of first expandable node (2subcoms)', () => { + expect(allNodes.length).toEqual(4); + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + mockSubcommunities1Page1.map((subcom) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === subcom.name); + })).toBeTruthy(); + }) + }); + }); + }); + + describe('second top community node is expanded and has more children (collections) than page size of collection', () => { + describe('children of second top com are added (page-limited pageSize 2)', () => { + let allNodes; + beforeEach(fakeAsync(() => { + const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button')); + const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span')); + if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) { + chevronExpand[1].nativeElement.click(); + tick(); + fixture.detectChanges(); + } + + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + allNodes = [...expandableNodesFound, ...childlessNodesFound]; + })); + it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => { + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + mockCollectionsPage1.map((coll) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === coll.name); + })).toBeTruthy(); + }) + expect(allNodes.length).toEqual(4); + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(2); + }); + }); + }); + }); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 5cce255380..622d03237e 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,76 +1,88 @@ -import {Component, OnInit} from '@angular/core'; -import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; -import {CommunityListDatasource} from '../community-list-datasource'; -import {FlatTreeControl} from '@angular/cdk/tree'; -import {Collection} from '../../core/shared/collection.model'; -import {Community} from '../../core/shared/community.model'; -import {isEmpty} from '../../shared/empty.util'; +import { Component, OnInit } from '@angular/core'; +import { CommunityListService, FlatNode } from '../community-list-service'; +import { CommunityListDatasource } from '../community-list-datasource'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { isEmpty } from '../../shared/empty.util'; @Component({ - selector: 'ds-community-list', - templateUrl: './community-list.component.html', - styleUrls: ['./community-list.component.scss'] + selector: 'ds-community-list', + templateUrl: './community-list.component.html', }) export class CommunityListComponent implements OnInit { - private expandedNodes: FlatNode[] = []; + private expandedNodes: FlatNode[] = []; + public loadingNode: FlatNode; - treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable - ); + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.isExpandable + ); - dataSource: CommunityListDatasource; + dataSource: CommunityListDatasource; - constructor(private communityListAdapter: CommunityListAdapter) { + constructor(private communityListService: CommunityListService) { + } + + ngOnInit() { + this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource.loadCommunities(null); + } + + // whether or not this node has children (subcommunities or collections) + hasChild(_: number, node: FlatNode) { + return node.isExpandable; + } + + // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections + isShowMore(_: number, node: FlatNode) { + return node.isShowMoreNode; + } + + /** + * Toggles the expanded variable of a node, adds it to the exapanded nodes list and reloads the tree so this node is expanded + * @param node Node we want to expand + */ + toggleExpanded(node: FlatNode) { + this.loadingNode = node; + if (node.isExpanded) { + this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); + node.isExpanded = false; + } else { + this.expandedNodes.push(node); + node.isExpanded = true; + if (isEmpty(node.currentCollectionPage)) { + node.currentCollectionPage = 1; + } + if (isEmpty(node.currentCommunityPage)) { + node.currentCommunityPage = 1; + } } + this.dataSource.loadCommunities(this.expandedNodes); + } - ngOnInit() { - this.dataSource = new CommunityListDatasource(this.communityListAdapter); - this.dataSource.loadCommunities(null); - } - - hasChild(_: number, node: FlatNode) { - return node.isExpandable; - } - - isShowMore(_: number, node: FlatNode) { - return node.isShowMoreNode; - } - - toggleExpanded(node: FlatNode) { - if (node.isExpanded) { - this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); - node.isExpanded = false; - } else { - this.expandedNodes.push(node); - node.isExpanded = true; - if (isEmpty(node.currentCollectionPage)) { - node.currentCollectionPage = 1; - } - if (isEmpty(node.currentCommunityPage)) { - node.currentCommunityPage = 1; - } + /** + * Makes sure the next page of a node is added to the tree (top community, sub community of collection) + * > Finds its parent (if not top community) and increases its corresponding collection/subcommunity currentPage + * > Reloads tree with new page added to corresponding top community lis, sub community list or collection list + * @param node The show more node indicating whether it's an increase in top communities, sub communities or collections + */ + getNextPage(node: FlatNode): void { + this.loadingNode = node; + if (node.parent != null) { + if (node.parent.isExpandable) { + if (node.id === 'collection') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; } - this.dataSource.loadCommunities(this.expandedNodes); - } - - getNextPage(node: FlatNode): void { - if (node.parent != null) { - if (node.parent.isExpandable) { - if (node.payload instanceof Collection) { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCollectionPage++; - } - if (node.payload instanceof Community) { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCommunityPage++; - } - } - this.dataSource.loadCommunities(this.expandedNodes); - } else { - this.communityListAdapter.getNextPageTopCommunities(); - this.dataSource.loadCommunities(this.expandedNodes); + if (node.id === 'community') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; } + } + this.dataSource.loadCommunities(this.expandedNodes); + } else { + this.communityListService.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); } + } } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 2e1eac9e9c..8902fdef5e 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { Observable } from 'rxjs/internal/Observable'; -import { FindListOptions, GetRequest } from './request.models'; +import {FindListOptions, FindListRequest, GetRequest} from './request.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { configureRequest } from '../shared/operators'; diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index ffefbba6f6..a7fcd205d4 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -13,7 +13,7 @@ import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; -import { of as observableOf } from 'rxjs'; +import {Observable, of as observableOf} from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; @@ -45,6 +45,11 @@ class TestService extends ComColDataService { ) { super(); } + + protected getFindByParentHref(parentUUID: string): Observable { + // implementation in subclasses for communities/collections + return undefined; + } } /* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index d28c975122..57bf64678f 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -23,7 +23,6 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; export class CommunityDataService extends ComColDataService { protected linkPath = 'communities'; protected topLinkPath = 'communities/search/top'; - protected subcommunitiesLinkPath = 'communities/search/subCommunities'; protected cds = this; constructor( @@ -57,20 +56,6 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } - findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { - const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); - - hrefObs.pipe( - filter((href: string) => hasValue(href)), - take(1)) - .subscribe((href: string) => { - const request = new FindListRequest(this.requestService.generateRequestId(), href, options); - this.requestService.configure(request); - }); - - return this.rdbService.buildList(hrefObs) as Observable>>; - } - protected getFindByParentHref(parentUUID: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( switchMap((communityEndpointHref: string) => diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 2518de9a7b..135547cdcc 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -128,9 +128,11 @@ export abstract class DataService { } findAll(options: FindListOptions = {}): Observable>> { - const hrefObs = this.getFindAllHref(options); + return this.findList(this.getFindAllHref(options), options); + } - hrefObs.pipe( + protected findList(href$, options: FindListOptions) { + href$.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindListRequest(this.requestService.generateRequestId(), href, options); @@ -140,7 +142,7 @@ export abstract class DataService { this.requestService.configure(request); }); - return this.rdbService.buildList(hrefObs) as Observable>>; + return this.rdbService.buildList(href$) as Observable>>; } /** diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index 4c7c3cd030..b2ba10fb98 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit { } as TextMenuItemModel, index: 0 }, - // { - // id: 'browse_global_communities_and_collections', - // parentID: 'browse_global', - // active: false, - // visible: true, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.browse_global_communities_and_collections', - // link: '#' - // } as LinkMenuItemModel, - // }, + /* Communities & Collections tree */ + { + id: `browse_global_communities_and_collections`, + parentID: 'browse_global', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: `menu.section.browse_global_communities_and_collections`, + link: `/community-list` + } as LinkMenuItemModel + }, /* Statistics */ { From eaf0911e2e36712cdf5fcc13487368043b1298de Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 19 Nov 2019 18:11:03 +0100 Subject: [PATCH 29/60] 66391: Fixed the valse positive chevron indicating node is expandable --- .../community-list-service.ts | 502 +++++++++--------- .../community-list.component.html | 2 +- .../community-list.component.ts | 20 +- 3 files changed, 261 insertions(+), 263 deletions(-) diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts index 6da1bff4e5..3cbb76f79c 100644 --- a/src/app/community-list-page/community-list-service.ts +++ b/src/app/community-list-page/community-list-service.ts @@ -1,288 +1,288 @@ -import { Injectable } from '@angular/core'; -import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; -import { Observable, of as observableOf } from 'rxjs'; -import { CommunityDataService } from '../core/data/community-data.service'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { Community } from '../core/shared/community.model'; -import { Collection } from '../core/shared/collection.model'; -import { hasValue, isNotEmpty } from '../shared/empty.util'; -import { RemoteData } from '../core/data/remote-data'; -import { PaginatedList } from '../core/data/paginated-list'; -import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; -import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; -import { CollectionDataService } from '../core/data/collection-data.service'; +import {Injectable} from '@angular/core'; +import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; +import {Observable, of as observableOf} from 'rxjs'; +import {CommunityDataService} from '../core/data/community-data.service'; +import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; +import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; +import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {Community} from '../core/shared/community.model'; +import {Collection} from '../core/shared/collection.model'; +import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {RemoteData} from '../core/data/remote-data'; +import {PaginatedList} from '../core/data/paginated-list'; +import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; +import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; +import {CollectionDataService} from '../core/data/collection-data.service'; export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection | ShowMoreFlatNode; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; + isExpandable$: Observable; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } export class ShowMoreFlatNode { } export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); export const toFlatNode = ( - c: Community | Collection, - isExpandable: boolean, - level: number, - isExpanded: boolean, - parent?: FlatNode + c: Community | Collection, + isExpandable: Observable, + level: number, + isExpanded: boolean, + parent?: FlatNode ): FlatNode => ({ - isExpandable: isExpandable, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), + isExpandable$: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); export const showMoreFlatNode = ( - id: string, - level: number, - parent: FlatNode + id: string, + level: number, + parent: FlatNode ): FlatNode => ({ - isExpandable: false, - name: 'Show More Flatnode', - id: id, - level: level, - isExpanded: false, - parent: parent, - payload: new ShowMoreFlatNode(), - isShowMoreNode: true, + isExpandable$: observableOf(false), + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, }); // tslint:disable-next-line:max-classes-per-file @Injectable() export class CommunityListService { - // page-limited list of top-level communities - payloads$: Array>>; + // page-limited list of top-level communities + payloads$: Array>>; - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - maxSubCommunitiesPerPage: number; - maxCollectionsPerPage: number; + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList(); + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); - this.maxSubCommunitiesPerPage = 3; - this.maxCollectionsPerPage = 3; - } + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } - /** - * Increases the payload so it contains the next page of top level communities - */ - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } - /** - * Gets all top communities, limited by page, and transforms this in a list of flatnodes. - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); - }); - return combineAndFlatten(res); - }; - - /** - * Puts the initial top level communities in a list to be called upon - */ - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } - - /** - * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity - * @param listOfPaginatedCommunities Paginated list of communities to be transformed - * @param level Level the tree is currently at - * @param parent FlatNode of the parent of this list of communities - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) + /** + * Gets all top communities, limited by page, and transforms this in a list of flatnodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; - } + return combineAndFlatten(res); + }; - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - /** - * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, - * followed by flatNodes of its possible subcommunities and collection - * It gets called recursively for each subcommunity to add its subcommunities and collections to the list - * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. - * @param community Community being transformed - * @param level Depth of the community in the list, subcommunities and collections go one level deeper - * @param parent Flatnode of the parent community - * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections - */ - public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const isExpandable = this.getIsExpandable(community); - - const communityFlatNode = toFlatNode(community, isExpandable, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms = []; - for (let i = 1; i <= currentCommunityPage; i++) { - const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { - elementsPerPage: this.maxSubCommunitiesPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); + map((results) => results.payload), + )]; + } - subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; - } + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } - obsList = [...obsList, combineAndFlatten(subcoms)]; + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } - const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; - let collections = []; - for (let i = 1; i <= currentCollectionPage; i++) { - const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { - elementsPerPage: this.maxCollectionsPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const isExpandable$ = this.getIsExpandable(community); + + const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * Returns an observable that combines the result.payload.totalElements fo the observables that the + * respective services return when queried + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): Observable { + let hasSubcoms$: Observable; + let hasColls$: Observable; + hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + let hasChildren$: Observable; + hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( take(1), - map((rd: RemoteData>) => { - let nodes = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, false, level + 1, false, communityFlatNode)); - if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { - nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; - } - return nodes; - }), - ); - collections = [...collections, nextSetOfCollectionsPage]; - } - obsList = [...obsList, combineAndFlatten(collections)]; + map((result: [boolean]) => { + if (result[0] || result[1]) { + return true; + } else { + return false; + } + }) + ); + + return hasChildren$; } - return combineAndFlatten(obsList); - } - - /** - * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 - * If it does, it is expandable => returns true, false if not - * @param community Community being checked whether it is expandable (if it has subcommunities or collections) - */ - public getIsExpandable(community: Community): boolean { - let isExpandable = true; - let nrOfSubcoms; - let nrOfColl; - this.communityDataService.findByParent(community.uuid, { elementsPerPage: 0 }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - ) - .subscribe((result) => { - if (result.payload.totalElements === 0) { - nrOfSubcoms = result.payload.totalElements; - } - ; - }); - - this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 0 }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - ) - .subscribe((result) => { - if (result.payload.totalElements === 0) { - nrOfColl = result.payload.totalElements; - } - ; - }); - if (nrOfSubcoms === 0 && nrOfColl === 0) { - isExpandable = false; - } - return isExpandable; - } - } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index fdec67d67c..9f8f511b12 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -28,7 +28,7 @@ diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 622d03237e..c51c4adde8 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -14,7 +14,7 @@ export class CommunityListComponent implements OnInit { public loadingNode: FlatNode; treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable + (node) => node.level, (node) => true ); dataSource: CommunityListDatasource; @@ -29,7 +29,7 @@ export class CommunityListComponent implements OnInit { // whether or not this node has children (subcommunities or collections) hasChild(_: number, node: FlatNode) { - return node.isExpandable; + return node.isExpandable$; } // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections @@ -68,15 +68,13 @@ export class CommunityListComponent implements OnInit { getNextPage(node: FlatNode): void { this.loadingNode = node; if (node.parent != null) { - if (node.parent.isExpandable) { - if (node.id === 'collection') { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCollectionPage++; - } - if (node.id === 'community') { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCommunityPage++; - } + if (node.id === 'collection') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; + } + if (node.id === 'community') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; } this.dataSource.loadCommunities(this.expandedNodes); } else { From 9bd1933548f6e32164e4b524e5d6072a57e55ec5 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 17:16:21 +0100 Subject: [PATCH 30/60] 66391: expandedNodes (&loadingNode) is now retrieved/sent to the store at component initialisation/destruction so the state of the tree persists & documentation --- src/app/app.reducer.ts | 5 +- .../community-list-datasource.ts | 7 +- .../community-list-page.module.ts | 3 + .../community-list-page.routing.module.ts | 3 + .../community-list-service.ts | 549 ++++++++++-------- .../community-list.actions.ts | 35 ++ .../community-list.reducer.ts | 36 ++ .../community-list.component.ts | 24 +- 8 files changed, 406 insertions(+), 256 deletions(-) create mode 100644 src/app/community-list-page/community-list.actions.ts create mode 100644 src/app/community-list-page/community-list.reducer.ts diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index bc84f961fb..f8c8b5df3d 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,5 +1,6 @@ import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store'; import * as fromRouter from '@ngrx/router-store'; +import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer'; import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; import { formReducer, FormState } from './shared/form/form.reducer'; import { @@ -43,6 +44,7 @@ export interface AppState { cssVariables: CSSVariablesState; menus: MenusState; objectSelection: ObjectSelectionListState; + communityList: CommunityListState; } export const appReducers: ActionReducerMap = { @@ -58,7 +60,8 @@ export const appReducers: ActionReducerMap = { truncatable: truncatableReducer, cssVariables: cssVariablesReducer, menus: menusReducer, - objectSelection: objectSelectionReducer + objectSelection: objectSelectionReducer, + communityList: CommunityListReducer, }; export const routerStateSelector = (state: AppState) => state.router; diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts index da4bcc00cd..3a9d9f2077 100644 --- a/src/app/community-list-page/community-list-datasource.ts +++ b/src/app/community-list-page/community-list-datasource.ts @@ -3,6 +3,12 @@ import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; import { BehaviorSubject, Observable, } from 'rxjs'; import { finalize, take, } from 'rxjs/operators'; +/** + * DataSource object needed by a CDK Tree to render its nodes. + * The list of FlatNodes that this DataSource object represents gets created in the CommunityListService at + * the beginning (initial page-limited top communities) and re-calculated any time the tree state changes + * (a node gets expanded or page-limited result become larger by triggering a show more node) + */ export class CommunityListDatasource implements DataSource { private communityList$ = new BehaviorSubject([]); @@ -12,7 +18,6 @@ export class CommunityListDatasource implements DataSource { } connect(collectionViewer: CollectionViewer): Observable { - this.loadCommunities(null); return this.communityList$.asObservable(); } diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 817e492bf7..2e3914fe03 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -6,6 +6,9 @@ import { CommunityListPageRoutingModule } from './community-list-page.routing.mo import { CommunityListComponent } from './community-list/community-list.component'; import { CdkTreeModule } from '@angular/cdk/tree'; +/** + * The page which houses a title and the community list, as described in community-list.component + */ @NgModule({ imports: [ CommonModule, diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index b226236af1..fe250cb96d 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -5,6 +5,9 @@ import { CdkTreeModule } from '@angular/cdk/tree'; import { CommunityListPageComponent } from './community-list-page.component'; import { CommunityListService } from './community-list-service'; +/** + * RouterModule to help navigate to the page with the community list tree + */ @NgModule({ imports: [ RouterModule.forChild([ diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts index 3cbb76f79c..a25dbd2689 100644 --- a/src/app/community-list-page/community-list-service.ts +++ b/src/app/community-list-page/community-list-service.ts @@ -1,288 +1,335 @@ -import {Injectable} from '@angular/core'; -import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of as observableOf} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; -import {Collection} from '../core/shared/collection.model'; -import {hasValue, isNotEmpty} from '../shared/empty.util'; -import {RemoteData} from '../core/data/remote-data'; -import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; -import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; -import {CollectionDataService} from '../core/data/collection-data.service'; +import { Injectable } from '@angular/core'; +import { createSelector, Store } from '@ngrx/store'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { Observable, of as observableOf } from 'rxjs'; +import { AppState } from '../app.reducer'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { PaginatedList } from '../core/data/paginated-list'; +import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; +import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; +import { CollectionDataService } from '../core/data/collection-data.service'; +import { CommunityListSaveAction } from './community-list.actions'; +import { CommunityListState } from './community-list.reducer'; +/** + * Each node in the tree is represented by a flatNode which contains info about the node itself and its position and + * state in the tree. There are nodes representing communities, collections and show more links. + */ export interface FlatNode { - isExpandable$: Observable; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection | ShowMoreFlatNode; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; + isExpandable$: Observable; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } +/** + * The show more links in the community tree are also represented by a flatNode so we know where in + * the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link) + */ export class ShowMoreFlatNode { } +// Helper method to combine an flatten an array of observables of flatNode arrays export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); +/** + * Creates a flatNode from a community or collection + * @param c The community or collection this flatNode represents + * @param isExpandable Whether or not this node is expandable (true if it has children) + * @param level Level indicating how deep in the tree this node should be rendered + * @param isExpanded Whether or not this node already is expanded + * @param parent Parent of this node (flatNode representing its parent community) + */ export const toFlatNode = ( - c: Community | Collection, - isExpandable: Observable, - level: number, - isExpanded: boolean, - parent?: FlatNode + c: Community | Collection, + isExpandable: Observable, + level: number, + isExpanded: boolean, + parent?: FlatNode ): FlatNode => ({ - isExpandable$: isExpandable, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), + isExpandable$: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); +/** + * Creates a show More flatnode where only the level and parent are of importance + */ export const showMoreFlatNode = ( - id: string, - level: number, - parent: FlatNode + id: string, + level: number, + parent: FlatNode ): FlatNode => ({ - isExpandable$: observableOf(false), - name: 'Show More Flatnode', - id: id, - level: level, - isExpanded: false, - parent: parent, - payload: new ShowMoreFlatNode(), - isShowMoreNode: true, + isExpandable$: observableOf(false), + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, }); +// Selectors the get the communityList data out of the store +const communityListStateSelector = (state: AppState) => state.communityList; +const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes); +const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode); + +/** + * Service class for the community list, responsible for the creating of the flat list used by communityList dataSource + * and connection to the store to retrieve and save the state of the community list + */ // tslint:disable-next-line:max-classes-per-file @Injectable() export class CommunityListService { - // page-limited list of top-level communities - payloads$: Array>>; + // page-limited list of top-level communities + payloads$: Array>>; - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - maxSubCommunitiesPerPage: number; - maxCollectionsPerPage: number; + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList(); + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService, + private store: Store) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); - this.maxSubCommunitiesPerPage = 3; - this.maxCollectionsPerPage = 3; - } + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } - /** - * Increases the payload so it contains the next page of top level communities - */ - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } + saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void { + this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode)); + } - /** - * Gets all top communities, limited by page, and transforms this in a list of flatnodes. - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); + getExpandedNodesFromStore(): Observable { + return this.store.select(expandedNodesSelector); + } + + getLoadingNodeFromStore(): Observable { + return this.store.select(loadingNodeSelector); + } + + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Gets all top communities, limited by page, and transforms this in a list of flatNodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subCommunities and collections need + * not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); + }); + return combineAndFlatten(res); + }; + + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) }); - return combineAndFlatten(res); - }; + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } - /** - * Puts the initial top level communities in a list to be called upon - */ - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); } - /** - * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity - * @param listOfPaginatedCommunities Paginated list of communities to be transformed - * @param level Level the tree is currently at - * @param parent FlatNode of the parent of this list of communities - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) - }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; - } + const isExpandable$ = this.getIsExpandable(community); - return combineAndFlatten(obsList); + const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * Returns an observable that combines the result.payload.totalElements fo the observables that the + * respective services return when queried + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): Observable { + let hasSubcoms$: Observable; + let hasColls$: Observable; + hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + let hasChildren$: Observable; + hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( + take(1), + map((result: [boolean]) => { + if (result[0] || result[1]) { + return true; } else { - return observableOf([]); + return false; } - } + }) + ); - /** - * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, - * followed by flatNodes of its possible subcommunities and collection - * It gets called recursively for each subcommunity to add its subcommunities and collections to the list - * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. - * @param community Community being transformed - * @param level Depth of the community in the list, subcommunities and collections go one level deeper - * @param parent Flatnode of the parent community - * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections - */ - public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const isExpandable$ = this.getIsExpandable(community); - - const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms = []; - for (let i = 1; i <= currentCommunityPage; i++) { - const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { - elementsPerPage: this.maxSubCommunitiesPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); - - subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; - } - - obsList = [...obsList, combineAndFlatten(subcoms)]; - - const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; - let collections = []; - for (let i = 1; i <= currentCollectionPage; i++) { - const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { - elementsPerPage: this.maxCollectionsPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => { - let nodes = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); - if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { - nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; - } - return nodes; - }), - ); - collections = [...collections, nextSetOfCollectionsPage]; - } - obsList = [...obsList, combineAndFlatten(collections)]; - } - - return combineAndFlatten(obsList); - } - - /** - * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 - * Returns an observable that combines the result.payload.totalElements fo the observables that the - * respective services return when queried - * @param community Community being checked whether it is expandable (if it has subcommunities or collections) - */ - public getIsExpandable(community: Community): Observable { - let hasSubcoms$: Observable; - let hasColls$: Observable; - hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((results) => results.payload.totalElements > 0), - ); - - hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((results) => results.payload.totalElements > 0), - ); - - let hasChildren$: Observable; - hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( - take(1), - map((result: [boolean]) => { - if (result[0] || result[1]) { - return true; - } else { - return false; - } - }) - ); - - return hasChildren$; - } + return hasChildren$; + } } diff --git a/src/app/community-list-page/community-list.actions.ts b/src/app/community-list-page/community-list.actions.ts new file mode 100644 index 0000000000..bfce6fba34 --- /dev/null +++ b/src/app/community-list-page/community-list.actions.ts @@ -0,0 +1,35 @@ +import { Action } from '@ngrx/store'; +import { type } from '../shared/ngrx/type'; +import { FlatNode } from './community-list-service'; + +/** + * All the action types of the community-list + */ + +export const CommunityListActionTypes = { + SAVE: type('dspace/community-list-page/SAVE') +}; + +/** + * Community list SAVE action + */ +export class CommunityListSaveAction implements Action { + + type = CommunityListActionTypes.SAVE; + + payload: { + expandedNodes: FlatNode[]; + loadingNode: FlatNode; + }; + + constructor(expandedNodes: FlatNode[], loadingNode: FlatNode) { + this.payload = { expandedNodes, loadingNode } + } +}; + +/** + * Export a type alias of all actions in this action group + * so that reducers can easily compose action types + */ + +export type CommunityListActions = CommunityListSaveAction; diff --git a/src/app/community-list-page/community-list.reducer.ts b/src/app/community-list-page/community-list.reducer.ts new file mode 100644 index 0000000000..ef53c7f94e --- /dev/null +++ b/src/app/community-list-page/community-list.reducer.ts @@ -0,0 +1,36 @@ +import { FlatNode } from './community-list-service'; +import { CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; + +/** + * States we wish to put in store concerning the community list + */ +export interface CommunityListState { + expandedNodes: FlatNode[]; + loadingNode: FlatNode; +} + +/** + * Initial starting state of the list of expandedNodes and the current loading node of the community list + */ +const initialState: CommunityListState = { + expandedNodes: [], + loadingNode: null, +}; + +/** + * Reducer to interact with store concerning objects for the community list + * @constructor + */ +export function CommunityListReducer(state = initialState, action: CommunityListSaveAction) { + switch (action.type) { + case CommunityListActionTypes.SAVE: { + return Object.assign({}, state, { + expandedNodes: (action as CommunityListSaveAction).payload.expandedNodes, + loadingNode: (action as CommunityListSaveAction).payload.loadingNode, + }) + } + default: { + return state; + } + } +} diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index c51c4adde8..ddcd49cd1c 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,14 +1,22 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { take } from 'rxjs/operators'; import { CommunityListService, FlatNode } from '../community-list-service'; import { CommunityListDatasource } from '../community-list-datasource'; import { FlatTreeControl } from '@angular/cdk/tree'; import { isEmpty } from '../../shared/empty.util'; +/** + * A tree-structured list of nodes representing the communities, their subCommunities and collections. + * Initially only the page-restricted top communities are shown. + * Each node can be expanded to show its children and all children are also page-limited. + * More pages of a page-limited result can be shown by pressing a show more node/link. + * Which nodes were expanded is kept in the store, so this persists across pages. + */ @Component({ selector: 'ds-community-list', templateUrl: './community-list.component.html', }) -export class CommunityListComponent implements OnInit { +export class CommunityListComponent implements OnInit, OnDestroy { private expandedNodes: FlatNode[] = []; public loadingNode: FlatNode; @@ -24,7 +32,17 @@ export class CommunityListComponent implements OnInit { ngOnInit() { this.dataSource = new CommunityListDatasource(this.communityListService); - this.dataSource.loadCommunities(null); + this.communityListService.getLoadingNodeFromStore().pipe(take(1)).subscribe((result) => { + this.loadingNode = result; + }); + this.communityListService.getExpandedNodesFromStore().pipe(take(1)).subscribe((result) => { + this.expandedNodes = [...result]; + this.dataSource.loadCommunities(this.expandedNodes); + }); + } + + ngOnDestroy(): void { + this.communityListService.saveCommunityListStateToStore(this.expandedNodes, this.loadingNode); } // whether or not this node has children (subcommunities or collections) From 69a99e9381fbb5e487d70ea3e8b35370b1d074c2 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 18:02:45 +0100 Subject: [PATCH 31/60] Fix after master rebase and tests updated for chevron rendering changes (AoT ok) --- .../related-items/related-items-component.ts | 6 ++-- .../community-list-service.spec.ts | 32 +++++++++++-------- .../community-list.component.spec.ts | 16 +++++----- src/app/core/data/bundle-data.service.ts | 4 +-- .../core/data/dso-redirect-data.service.ts | 4 +-- src/app/core/data/relationship.service.ts | 12 +++---- 6 files changed, 40 insertions(+), 34 deletions(-) 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 9cbfd2c28d..ff5eb00c48 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 @@ -4,7 +4,7 @@ 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 { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; import { Subscription } from 'rxjs/internal/Subscription'; import { ViewMode } from '../../../core/shared/view-mode.model'; @@ -33,7 +33,7 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { * Default options to start a search request with * Optional input, should you wish a different page size (or other options) */ - @Input() options = Object.assign(new FindAllOptions(), { elementsPerPage: 5 }); + @Input() options = Object.assign(new FindListOptions(), { elementsPerPage: 5 }); /** * An i18n label to use as a title for the list (usually describes the relation) @@ -53,7 +53,7 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { /** * Search options for displaying all elements in a list */ - allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 }); + allOptions = Object.assign(new FindListOptions(), { elementsPerPage: 9999 }); /** * The view-mode we're currently on diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts index c3cb24cc59..e55903a8c8 100644 --- a/src/app/community-list-page/community-list-service.spec.ts +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -1,4 +1,8 @@ -import { TestBed, inject, async, fakeAsync } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs'; +import { TestBed, inject, async } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; +import { AppState } from '../app.reducer'; +import { MockStore } from '../shared/testing/mock-store'; import { CommunityListService, FlatNode, toFlatNode } from './community-list-service'; import { CollectionDataService } from '../core/data/collection-data.service'; import { PaginatedList } from '../core/data/paginated-list'; @@ -10,10 +14,11 @@ import { } from '../shared/testing/utils'; import { Community } from '../core/shared/community.model'; import { Collection } from '../core/shared/collection.model'; -import { map, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { FindListOptions } from '../core/data/request.models'; describe('CommunityListService', () => { + let store: MockStore; const standardElementsPerPage = 2; let collectionDataServiceStub: any; let communityDataServiceStub: any; @@ -179,10 +184,11 @@ describe('CommunityListService', () => { { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: CommunityDataService, useValue: communityDataServiceStub },], }); - service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub); + store = TestBed.get(Store); + service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store); })); - afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub)); + afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store)); it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => { expect(serviceIn).toBeTruthy(); @@ -242,7 +248,7 @@ describe('CommunityListService', () => { beforeEach(() => { const expandedNodes = []; mockListOfTopCommunitiesPage1.map((community: Community) => { - const communityFlatNode = toFlatNode(community, true, 0, true, null); + const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; expandedNodes.push(communityFlatNode); @@ -265,7 +271,7 @@ describe('CommunityListService', () => { }); describe('Just first top comm expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { beforeEach(() => { - const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], true, 0, true, null); + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -284,7 +290,7 @@ describe('CommunityListService', () => { }); describe('Just second top comm expanded, collections at page 2: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { beforeEach(() => { - const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], true, 0, true, null); + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 2; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -336,7 +342,7 @@ describe('CommunityListService', () => { beforeEach(() => { const expandedNodes = []; listOfCommunities.map((community: Community) => { - const communityFlatNode = toFlatNode(community, true, 0, true, null); + const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; expandedNodes.push(communityFlatNode); @@ -436,7 +442,7 @@ describe('CommunityListService', () => { }); let flatNodeList; beforeEach(() => { - const communityFlatNode = toFlatNode(communityWithSubcoms, true, 0, true, null); + const communityFlatNode = toFlatNode(communityWithSubcoms, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -475,7 +481,7 @@ describe('CommunityListService', () => { }); let flatNodeList; beforeEach(() => { - const communityFlatNode = toFlatNode(communityWithCollections, true, 0, true, null); + const communityFlatNode = toFlatNode(communityWithCollections, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 2; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -523,7 +529,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 1' }] } }); - expect(service.getIsExpandable(communityWithSubcoms)).toEqual(true); + expect(service.getIsExpandable(communityWithSubcoms)).toEqual(observableOf(true)); }); it('if community has collections', () => { const communityWithCollections = Object.assign(new Community(), { @@ -536,7 +542,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 2' }] } }); - expect(service.getIsExpandable(communityWithCollections)).toEqual(true); + expect(service.getIsExpandable(communityWithCollections)).toEqual(observableOf(true)); }); }); describe('should return false', () => { @@ -551,7 +557,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 3' }] } }); - expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(false); + expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(observableOf(false)); }); }); diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index c1c9285491..448c6d5b9f 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -17,7 +17,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; import { Collection } from '../../core/shared/collection.model'; -import { Observable, of as observableOf } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { By } from '@angular/platform-browser'; import { isEmpty, isNotEmpty } from '../../shared/empty.util'; @@ -89,7 +89,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), name: 'community1', - }), true, 0, false, null + }), observableOf(true), 0, false, null ), toFlatNode( Object.assign(new Community(), { @@ -98,7 +98,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), name: 'community2', - }), true, 0, false, null + }), observableOf(true), 0, false, null ), toFlatNode( Object.assign(new Community(), { @@ -107,7 +107,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), name: 'community3', - }), false, 0, false, null + }), observableOf(false), 0, false, null ), ]; let communityListServiceStub; @@ -143,7 +143,7 @@ describe('CommunityListComponent', () => { flatnodes = []; const topFlatnodes = mockTopFlatnodesUnexpanded.slice(0, endPageIndex); topFlatnodes.map((topNode: FlatNode) => { - flatnodes = [...flatnodes, topNode] + flatnodes = [...flatnodes, topNode]; const expandedParent: FlatNode = expandedNodes.find((expandedNode: FlatNode) => expandedNode.id === topNode.id); if (isNotEmpty(expandedParent)) { const matchingTopComWithArrays = mockTopCommunitiesWithChildrenArrays.find((topcom) => topcom.id === topNode.id); @@ -151,12 +151,12 @@ describe('CommunityListComponent', () => { const possibleSubcoms: Community[] = matchingTopComWithArrays.subcommunities; let subComFlatnodes = []; possibleSubcoms.map((subcom: Community) => { - subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, false, topNode.level + 1, false, topNode)]; + subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, observableOf(false), topNode.level + 1, false, topNode)]; }); const possibleColls: Collection[] = matchingTopComWithArrays.collections; let collFlatnodes = []; possibleColls.map((coll: Collection) => { - collFlatnodes = [...collFlatnodes, toFlatNode(coll, false, topNode.level + 1, false, topNode)]; + collFlatnodes = [...collFlatnodes, toFlatNode(coll, observableOf(false), topNode.level + 1, false, topNode)]; }); if (isNotEmpty(subComFlatnodes)) { const endSubComIndex = this.subcommunityPageSize * expandedParent.currentCommunityPage; @@ -313,7 +313,7 @@ describe('CommunityListComponent', () => { expect(allNodes.find((foundEl) => { return (foundEl.nativeElement.textContent.trim() === coll.name); })).toBeTruthy(); - }) + }); expect(allNodes.length).toEqual(4); const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); expect(showMoreEl.length).toEqual(2); diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index 5962488c4f..a66f4124ee 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; /** @@ -40,7 +40,7 @@ export class BundleDataService extends DataService { * @param {FindAllOptions} options * @returns {Observable} */ - getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { return this.halService.getEndpoint(this.linkPath); } } diff --git a/src/app/core/data/dso-redirect-data.service.ts b/src/app/core/data/dso-redirect-data.service.ts index 7e71f82bbf..f4999637b3 100644 --- a/src/app/core/data/dso-redirect-data.service.ts +++ b/src/app/core/data/dso-redirect-data.service.ts @@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { RequestService } from './request.service'; import { Store } from '@ngrx/store'; import { CoreState } from '../core.reducers'; -import { FindAllOptions, FindByIDRequest, IdentifierType } from './request.models'; +import { FindListOptions, FindByIDRequest, IdentifierType } from './request.models'; import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; @@ -40,7 +40,7 @@ export class DsoRedirectDataService extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index c466bd15af..9cd71b2853 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -10,7 +10,7 @@ import { getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators'; -import { DeleteRequest, FindAllOptions, RestRequest } from './request.models'; +import { DeleteRequest, FindListOptions, RestRequest } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; import { RestResponse } from '../cache/response.models'; import { Item } from '../shared/item.model'; @@ -56,7 +56,7 @@ export class RelationshipService extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } @@ -228,7 +228,7 @@ export class RelationshipService extends DataService { * @param label * @param options */ - getRelatedItemsByLabel(item: Item, label: string, options?: FindAllOptions): Observable>> { + getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { return this.getItemRelationshipsByLabel(item, label, options).pipe(paginatedRelationsToItems(item.uuid)); } @@ -239,10 +239,10 @@ export class RelationshipService extends DataService { * @param label * @param options */ - getItemRelationshipsByLabel(item: Item, label: string, options?: FindAllOptions): Observable>> { - let findAllOptions = new FindAllOptions(); + getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { + let findAllOptions = new FindListOptions(); if (options) { - findAllOptions = Object.assign(new FindAllOptions(), options); + findAllOptions = Object.assign(new FindListOptions(), options); } const searchParams = [ new SearchParam('label', label), new SearchParam('dso', item.id) ]; if (findAllOptions.searchParams) { From 5ff634a26facf8362f36e1afbde3f93f5180d627 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 18:06:07 +0100 Subject: [PATCH 32/60] Last bit of doc --- src/app/community-list-page/community-list-page.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts index 9ba715c20a..5ab3cce5de 100644 --- a/src/app/community-list-page/community-list-page.component.ts +++ b/src/app/community-list-page/community-list-page.component.ts @@ -1,5 +1,9 @@ import { Component } from '@angular/core'; +/** + * Page with title and the community list tree, as described in community-list.component; + * navigated to with community-list.page.routing.module + */ @Component({ selector: 'ds-community-list-page', templateUrl: './community-list-page.component.html', From db7ebfb16e24dd01261adb1776da4d7c035c6020 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 27 Nov 2019 10:37:38 +0100 Subject: [PATCH 33/60] Existing community list test fixes --- .../community-list-service.spec.ts | 18 +++++++++++++----- .../community-list.component.spec.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts index e55903a8c8..a150277d20 100644 --- a/src/app/community-list-page/community-list-service.spec.ts +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -117,7 +117,7 @@ describe('CommunityListService', () => { beforeEach(async(() => { communityDataServiceStub = { findTop(options: FindListOptions = {}) { - const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2] + const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2]; let currentPage = options.currentPage; const elementsPerPage = 3; if (currentPage === undefined) { @@ -182,7 +182,9 @@ describe('CommunityListService', () => { TestBed.configureTestingModule({ providers: [CommunityListService, { provide: CollectionDataService, useValue: collectionDataServiceStub }, - { provide: CommunityDataService, useValue: communityDataServiceStub },], + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: Store, useValue: MockStore }, + ], }); store = TestBed.get(Store); service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store); @@ -529,7 +531,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 1' }] } }); - expect(service.getIsExpandable(communityWithSubcoms)).toEqual(observableOf(true)); + service.getIsExpandable(communityWithSubcoms).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(true); + }); }); it('if community has collections', () => { const communityWithCollections = Object.assign(new Community(), { @@ -542,7 +546,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 2' }] } }); - expect(service.getIsExpandable(communityWithCollections)).toEqual(observableOf(true)); + service.getIsExpandable(communityWithCollections).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(true); + }); }); }); describe('should return false', () => { @@ -557,7 +563,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 3' }] } }); - expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(observableOf(false)); + service.getIsExpandable(communityWithNoSubcomsOrColls).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(false); + }); }); }); diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index 448c6d5b9f..c04aadda37 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -118,9 +118,21 @@ describe('CommunityListComponent', () => { topCurrentPage: 1, collectionPageSize: 2, subcommunityPageSize: 2, + expandedNodes: [], + loadingNode: null, getNextPageTopCommunities() { this.topCurrentPage++; }, + getLoadingNodeFromStore() { + return observableOf(this.loadingNode); + }, + getExpandedNodesFromStore() { + return observableOf(this.expandedNodes); + }, + saveCommunityListStateToStore(expandedNodes, loadingNode) { + this.expandedNodes = expandedNodes; + this.loadingNode = loadingNode; + }, loadCommunities(expandedNodes) { let flatnodes; let showMoreTopComNode = false; From 36e80e3624e4a09c5f2ca390dbda9060ad3e02a6 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:30:26 +0100 Subject: [PATCH 34/60] Add support for OTF fonts --- webpack/webpack.common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 806c0c6f40..f70040c647 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -173,7 +173,7 @@ module.exports = (env) => { ] }, { - test: /\.(html|eot|ttf|svg|woff|woff2)$/, + test: /\.(html|eot|ttf|otf|svg|woff|woff2)$/, loader: 'raw-loader' } ] From c0962cb9669738525dc2d7be83056394069841ed Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:32:30 +0100 Subject: [PATCH 35/60] add supported fonts README --- resources/fonts/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/fonts/README.md diff --git a/resources/fonts/README.md b/resources/fonts/README.md new file mode 100644 index 0000000000..5f7e5ab777 --- /dev/null +++ b/resources/fonts/README.md @@ -0,0 +1,3 @@ +# Supported font formats + +DSpace 7 supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. \ No newline at end of file From afcf897bbe911b85800e4a7195fd12210a7c05b0 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 27 Nov 2019 13:37:37 +0100 Subject: [PATCH 36/60] Reducer test and e2e test for persisting tree state through store --- .../community-list-page.e2e-spec.ts | 28 ++++++++++++ .../community-list-page.po.ts | 27 +++++++++++ .../community-list.reducer.spec.ts | 45 +++++++++++++++++++ .../community-list.reducer.ts | 4 +- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 e2e/community-list-page/community-list-page.e2e-spec.ts create mode 100644 e2e/community-list-page/community-list-page.po.ts create mode 100644 src/app/community-list-page/community-list.reducer.spec.ts diff --git a/e2e/community-list-page/community-list-page.e2e-spec.ts b/e2e/community-list-page/community-list-page.e2e-spec.ts new file mode 100644 index 0000000000..84c7257726 --- /dev/null +++ b/e2e/community-list-page/community-list-page.e2e-spec.ts @@ -0,0 +1,28 @@ +import { CommunityListPageProtractor } from './community-list-page.po'; + +describe('protractor CommunityListPage', () => { + let page: CommunityListPageProtractor; + + beforeEach(() => { + page = new CommunityListPageProtractor(); + }); + + it('should contain page-limited top communities (at least 1 expandable community)', () => { + page.navigateToCommunityList(); + expect(page.anExpandableCommunityIsPresent()).toEqual(true) + }); + + describe('if expanded a node and navigating away, tree state gets saved', () => { + it('if navigating back, same node is expanded', () => { + page.navigateToCommunityList(); + const linkOfSecondNodeBeforeExpanding = page.getLinkOfSecondNode(); + page.toggleExpandFirstExpandableCommunity(); + const linkOfSecondNodeAfterExpanding = page.getLinkOfSecondNode(); + page.navigateToHome(); + page.navigateToCommunityList(); + expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeAfterExpanding); + page.toggleExpandFirstExpandableCommunity(); + expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeBeforeExpanding); + }); + }); +}); diff --git a/e2e/community-list-page/community-list-page.po.ts b/e2e/community-list-page/community-list-page.po.ts new file mode 100644 index 0000000000..ff8a2eeba7 --- /dev/null +++ b/e2e/community-list-page/community-list-page.po.ts @@ -0,0 +1,27 @@ +import { browser, by, element } from 'protractor'; + +export class CommunityListPageProtractor { + HOMEPAGE = '/home'; + COMMUNITY_LIST = '/community-list'; + + navigateToHome() { + return browser.get(this.HOMEPAGE); + } + + navigateToCommunityList() { + return browser.get(this.COMMUNITY_LIST); + } + + anExpandableCommunityIsPresent() { + return element(by.css('.expandable-node h5 a')).isPresent(); + } + + toggleExpandFirstExpandableCommunity() { + element(by.css('.expandable-node button')).click(); + } + + getLinkOfSecondNode() { + return element(by.css('.cdk-tree-node h5 a')).getAttribute('href'); + } + +} diff --git a/src/app/community-list-page/community-list.reducer.spec.ts b/src/app/community-list-page/community-list.reducer.spec.ts new file mode 100644 index 0000000000..63eaaccc03 --- /dev/null +++ b/src/app/community-list-page/community-list.reducer.spec.ts @@ -0,0 +1,45 @@ +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { PaginatedList } from '../core/data/paginated-list'; +import { Community } from '../core/shared/community.model'; +import { PageInfo } from '../core/shared/page-info.model'; +import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils'; +import { toFlatNode } from './community-list-service'; +import { CommunityListSaveAction } from './community-list.actions'; +import { CommunityListReducer } from './community-list.reducer'; + +describe('communityListReducer', () => { + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + name: 'subcommunity1', + })]; + const mockFlatNodeOfCommunity = toFlatNode( + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community1', + }), observableOf(true), 0, false, null + ); + + it ('should set init state of the expandedNodes and loadingNode', () => { + const state = { + expandedNodes: [], + loadingNode: null, + }; + const action = new CommunityListSaveAction([], null); + const newState = CommunityListReducer(null, action); + expect(newState).toEqual(state); + }); + + it ('should save new state of the expandedNodes and loadingNode at a save action', () => { + const state = { + expandedNodes: [mockFlatNodeOfCommunity], + loadingNode: null, + }; + const action = new CommunityListSaveAction([mockFlatNodeOfCommunity], null); + const newState = CommunityListReducer(null, action); + expect(newState).toEqual(state); + }); +}); diff --git a/src/app/community-list-page/community-list.reducer.ts b/src/app/community-list-page/community-list.reducer.ts index ef53c7f94e..b455fc496a 100644 --- a/src/app/community-list-page/community-list.reducer.ts +++ b/src/app/community-list-page/community-list.reducer.ts @@ -1,5 +1,5 @@ import { FlatNode } from './community-list-service'; -import { CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; +import { CommunityListActions, CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; /** * States we wish to put in store concerning the community list @@ -21,7 +21,7 @@ const initialState: CommunityListState = { * Reducer to interact with store concerning objects for the community list * @constructor */ -export function CommunityListReducer(state = initialState, action: CommunityListSaveAction) { +export function CommunityListReducer(state = initialState, action: CommunityListActions) { switch (action.type) { case CommunityListActionTypes.SAVE: { return Object.assign({}, state, { From d127e5f27cfa6e81b967180cbed291d90e9a09a7 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:48:06 +0100 Subject: [PATCH 37/60] remove version number --- resources/fonts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/fonts/README.md b/resources/fonts/README.md index 5f7e5ab777..e4817b8572 100644 --- a/resources/fonts/README.md +++ b/resources/fonts/README.md @@ -1,3 +1,3 @@ # Supported font formats -DSpace 7 supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. \ No newline at end of file +DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. From 8c2e63c2a7829c6a36cf14d0fdc288d96d6748cf Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 14:55:18 +0100 Subject: [PATCH 38/60] fix issue where font urls in scss slow down the build --- package.json | 2 +- webpack/webpack.common.js | 26 ++++++------- yarn.lock | 78 ++++++++++++++------------------------- 3 files changed, 41 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 3a54b941dd..ec6c025d76 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "rollup-plugin-node-globals": "1.2.1", "rollup-plugin-node-resolve": "^3.0.3", "rollup-plugin-terser": "^2.0.2", - "sass-loader": "7.1.0", + "sass-loader": "^7.1.0", "script-ext-html-webpack-plugin": "2.0.1", "source-map": "0.7.3", "source-map-loader": "0.2.4", diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index f70040c647..e63ae024ed 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -119,12 +119,6 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { @@ -132,6 +126,12 @@ module.exports = (env) => { includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, { loader: 'sass-resources-loader', options: { @@ -157,19 +157,19 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { sourceMap: true, includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } - } + }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, ] }, { diff --git a/yarn.lock b/yarn.lock index 69f4a072ae..5d7801aa4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2093,15 +2093,14 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -clone-deep@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" - integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: - for-own "^1.0.0" is-plain-object "^2.0.4" - kind-of "^6.0.0" - shallow-clone "^1.0.0" + kind-of "^6.0.2" + shallow-clone "^3.0.0" clone-stats@^0.0.1: version "0.0.1" @@ -4121,11 +4120,6 @@ font-awesome@4.7.0: resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4138,13 +4132,6 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6148,7 +6135,7 @@ loader-utils@^0.2.12, loader-utils@^0.2.15, loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= @@ -6157,7 +6144,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1 emojis-list "^2.0.0" json5 "^0.5.0" -loader-utils@^1.0.4: +loader-utils@^1.0.1, loader-utils@^1.0.4: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" dependencies: @@ -6371,11 +6358,6 @@ lodash.startswith@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" integrity sha1-xZjErc4YiiflMUVzHNxsDnF3YAw= -lodash.tail@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" - integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= - lodash.template@^3.0.0: version "3.6.2" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" @@ -6849,14 +6831,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -9654,17 +9628,16 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-loader@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" - integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w== +sass-loader@^7.1.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f" + integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA== dependencies: - clone-deep "^2.0.1" + clone-deep "^4.0.1" loader-utils "^1.0.1" - lodash.tail "^4.1.1" neo-async "^2.5.0" - pify "^3.0.0" - semver "^5.5.0" + pify "^4.0.1" + semver "^6.3.0" sass-resources-loader@^2.0.0: version "2.0.0" @@ -9769,7 +9742,7 @@ semver-intersect@^1.1.2: dependencies: semver "^5.0.0" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: +"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== @@ -9779,7 +9752,12 @@ semver@^5.0.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^6.1.1: +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.1, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -9910,14 +9888,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" - integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: - is-extendable "^0.1.1" - kind-of "^5.0.0" - mixin-object "^2.0.1" + kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" From 1b978124d1ef7e44c0e6ab27d3809ae965834611 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 27 Nov 2019 18:01:31 +0100 Subject: [PATCH 39/60] 66156: Removal of redundant fields, message update and aciveTab as observable --- resources/i18n/en.json5 | 4 ++-- .../tabbed-related-entities-search.component.html | 2 +- .../tabbed-related-entities-search.component.spec.ts | 11 +++++------ .../tabbed-related-entities-search.component.ts | 8 ++++++-- .../item-pages/org-unit/org-unit.component.html | 10 ---------- .../item-pages/org-unit/org-unit.component.html | 12 ------------ 6 files changed, 14 insertions(+), 33 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7e250490fc..4a47434f21 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -822,9 +822,9 @@ "item.page.relationships.isJournalOfPublication": "Publications", - "item.page.relationships.isOrgUnitOfPerson": "Persons", + "item.page.relationships.isOrgUnitOfPerson": "Authors", - "item.page.relationships.isOrgUnitOfProject": "Projects", + "item.page.relationships.isOrgUnitOfProject": "Research Projects", "item.page.subject": "Keywords", diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index d3690c74be..f9642d2c01 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts index bc767476e1..2d2e682196 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts @@ -8,6 +8,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { MockRouter } from '../../../../shared/mocks/mock-router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { VarDirective } from '../../../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; describe('TabbedRelatedEntitiesSearchComponent', () => { let comp: TabbedRelatedEntitiesSearchComponent; @@ -34,11 +35,7 @@ describe('TabbedRelatedEntitiesSearchComponent', () => { { provide: ActivatedRoute, useValue: { - snapshot: { - queryParams: { - tab: mockRelationType - } - } + queryParams: observableOf({ tab: mockRelationType }) }, }, { provide: Router, useValue: router } @@ -56,7 +53,9 @@ describe('TabbedRelatedEntitiesSearchComponent', () => { }); it('should initialize the activeTab depending on the current query parameters', () => { - expect(comp.activeTab).toEqual(mockRelationType); + comp.activeTab$.subscribe((activeTab) => { + expect(activeTab).toEqual(mockRelationType); + }); }); describe('onTabChange', () => { diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index 9fc4e7ec34..b01eb70720 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -1,6 +1,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-tabbed-related-entities-search', @@ -42,7 +44,7 @@ export class TabbedRelatedEntitiesSearchComponent implements OnInit { /** * The active tab */ - activeTab: string; + activeTab$: Observable; constructor(private route: ActivatedRoute, private router: Router) { @@ -52,7 +54,9 @@ export class TabbedRelatedEntitiesSearchComponent implements OnInit { * If the url contains a "tab" query parameter, set this tab to be the active tab */ ngOnInit(): void { - this.activeTab = this.route.snapshot.queryParams.tab; + this.activeTab$ = this.route.queryParams.pipe( + map((params) => params.tab) + ); } /** diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index c9227338eb..1b23d567f5 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -24,16 +24,6 @@
- - - -
- - - - Date: Fri, 29 Nov 2019 10:17:11 +0100 Subject: [PATCH 40/60] 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 4896c98f9ab65c2f47407ce9ef1a637dd85d5577 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 2 Dec 2019 13:46:34 +0100 Subject: [PATCH 41/60] apply fix to test webpack config as well --- webpack/webpack.test.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js index 83e6e44e79..de53de31c4 100644 --- a/webpack/webpack.test.js +++ b/webpack/webpack.test.js @@ -160,12 +160,6 @@ module.exports = function (env) { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { @@ -173,6 +167,12 @@ module.exports = function (env) { includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, { loader: 'sass-resources-loader', options: { @@ -198,19 +198,19 @@ module.exports = function (env) { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { sourceMap: true, includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } - } + }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, ] }, From 8cacad3264c3b332562a10465dd72134a315acb9 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 11:09:00 +0100 Subject: [PATCH 42/60] e2e test fix, so it waits for the loading element to disappear (tree is rendered) --- e2e/community-list-page/community-list-page.po.ts | 9 +++++++-- .../community-list/community-list.component.html | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/e2e/community-list-page/community-list-page.po.ts b/e2e/community-list-page/community-list-page.po.ts index ff8a2eeba7..332aa5a80a 100644 --- a/e2e/community-list-page/community-list-page.po.ts +++ b/e2e/community-list-page/community-list-page.po.ts @@ -1,4 +1,4 @@ -import { browser, by, element } from 'protractor'; +import { browser, by, element, protractor } from 'protractor'; export class CommunityListPageProtractor { HOMEPAGE = '/home'; @@ -9,10 +9,15 @@ export class CommunityListPageProtractor { } navigateToCommunityList() { - return browser.get(this.COMMUNITY_LIST); + browser.get(this.COMMUNITY_LIST); + const loading = element(by.css('.ds-loading')); + browser.wait(protractor.ExpectedConditions.invisibilityOf(loading), 10000); + return; + } anExpandableCommunityIsPresent() { + console.log(element(by.css('body'))); return element(by.css('.expandable-node h5 a')).isPresent(); } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 9f8f511b12..c179715bf1 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,4 +1,4 @@ - + @@ -13,7 +13,7 @@ class="btn btn-outline-secondary btn-sm"> {{ 'communityList.showMore' | translate }} - +
@@ -57,7 +57,7 @@ - +
From e77866a0d286002def39a2489368506c1b2a678d Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 14:19:31 +0100 Subject: [PATCH 43/60] Merged Dspace/dspace-angular/master and changed all new FindAllOptions references to FindListOptions --- src/app/core/data/bundle-data.service.ts | 2 +- src/app/core/data/relationship.service.ts | 12 ++++++------ src/app/core/data/site-data.service.spec.ts | 4 ++-- src/app/core/data/site-data.service.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index a66f4124ee..280f727aad 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -37,7 +37,7 @@ export class BundleDataService extends DataService { /** * Get the endpoint for browsing bundles - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 9cd71b2853..f102e2ca53 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -240,17 +240,17 @@ export class RelationshipService extends DataService { * @param options */ getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { - let findAllOptions = new FindListOptions(); + let findListOptions = new FindListOptions(); if (options) { - findAllOptions = Object.assign(new FindListOptions(), options); + findListOptions = Object.assign(new FindListOptions(), options); } const searchParams = [ new SearchParam('label', label), new SearchParam('dso', item.id) ]; - if (findAllOptions.searchParams) { - findAllOptions.searchParams = [...findAllOptions.searchParams, ...searchParams]; + if (findListOptions.searchParams) { + findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams]; } else { - findAllOptions.searchParams = searchParams; + findListOptions.searchParams = searchParams; } - return this.searchBy('byLabel', findAllOptions); + return this.searchBy('byLabel', findListOptions); } /** diff --git a/src/app/core/data/site-data.service.spec.ts b/src/app/core/data/site-data.service.spec.ts index 189218b5cf..09fa7fb457 100644 --- a/src/app/core/data/site-data.service.spec.ts +++ b/src/app/core/data/site-data.service.spec.ts @@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { of as observableOf } from 'rxjs'; import { RestResponse } from '../cache/response.models'; import { RequestEntry } from './request.reducer'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { TestScheduler } from 'rxjs/testing'; import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; @@ -31,7 +31,7 @@ describe('SiteDataService', () => { }); const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2'; - const options = Object.assign(new FindAllOptions(), {}); + const options = Object.assign(new FindListOptions(), {}); const getRequestEntry$ = (successful:boolean, statusCode:number, statusText:string) => { return observableOf({ diff --git a/src/app/core/data/site-data.service.ts b/src/app/core/data/site-data.service.ts index 4993d47226..7550594cda 100644 --- a/src/app/core/data/site-data.service.ts +++ b/src/app/core/data/site-data.service.ts @@ -10,7 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { RemoteData } from './remote-data'; @@ -46,10 +46,10 @@ export class SiteDataService extends DataService { /** * Get the endpoint for browsing the site object - * @param {FindAllOptions} options + * @param {FindListOptions} options * @param {Observable} linkPath */ - getBrowseEndpoint(options:FindAllOptions, linkPath?:string):Observable { + getBrowseEndpoint(options:FindListOptions, linkPath?:string):Observable { return this.halService.getEndpoint(this.linkPath); } From 43680155670a7c7b96850dd9d665e81f05d97562 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 15:03:07 +0100 Subject: [PATCH 44/60] e2e tests for community-list removed --- .../community-list-page.e2e-spec.ts | 28 ---------------- .../community-list-page.po.ts | 32 ------------------- 2 files changed, 60 deletions(-) delete mode 100644 e2e/community-list-page/community-list-page.e2e-spec.ts delete mode 100644 e2e/community-list-page/community-list-page.po.ts diff --git a/e2e/community-list-page/community-list-page.e2e-spec.ts b/e2e/community-list-page/community-list-page.e2e-spec.ts deleted file mode 100644 index 84c7257726..0000000000 --- a/e2e/community-list-page/community-list-page.e2e-spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CommunityListPageProtractor } from './community-list-page.po'; - -describe('protractor CommunityListPage', () => { - let page: CommunityListPageProtractor; - - beforeEach(() => { - page = new CommunityListPageProtractor(); - }); - - it('should contain page-limited top communities (at least 1 expandable community)', () => { - page.navigateToCommunityList(); - expect(page.anExpandableCommunityIsPresent()).toEqual(true) - }); - - describe('if expanded a node and navigating away, tree state gets saved', () => { - it('if navigating back, same node is expanded', () => { - page.navigateToCommunityList(); - const linkOfSecondNodeBeforeExpanding = page.getLinkOfSecondNode(); - page.toggleExpandFirstExpandableCommunity(); - const linkOfSecondNodeAfterExpanding = page.getLinkOfSecondNode(); - page.navigateToHome(); - page.navigateToCommunityList(); - expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeAfterExpanding); - page.toggleExpandFirstExpandableCommunity(); - expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeBeforeExpanding); - }); - }); -}); diff --git a/e2e/community-list-page/community-list-page.po.ts b/e2e/community-list-page/community-list-page.po.ts deleted file mode 100644 index 332aa5a80a..0000000000 --- a/e2e/community-list-page/community-list-page.po.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { browser, by, element, protractor } from 'protractor'; - -export class CommunityListPageProtractor { - HOMEPAGE = '/home'; - COMMUNITY_LIST = '/community-list'; - - navigateToHome() { - return browser.get(this.HOMEPAGE); - } - - navigateToCommunityList() { - browser.get(this.COMMUNITY_LIST); - const loading = element(by.css('.ds-loading')); - browser.wait(protractor.ExpectedConditions.invisibilityOf(loading), 10000); - return; - - } - - anExpandableCommunityIsPresent() { - console.log(element(by.css('body'))); - return element(by.css('.expandable-node h5 a')).isPresent(); - } - - toggleExpandFirstExpandableCommunity() { - element(by.css('.expandable-node button')).click(); - } - - getLinkOfSecondNode() { - return element(by.css('.cdk-tree-node h5 a')).getAttribute('href'); - } - -} From 7b31ad0345b7fcdd7e23cd3bef2d0677f79c087c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 17:48:42 +0100 Subject: [PATCH 45/60] [DS-4400] Fix for minor edit metadata bug --- src/app/core/cache/models/normalized-item.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts index c613c59a0c..9b7edf70c0 100644 --- a/src/app/core/cache/models/normalized-item.model.ts +++ b/src/app/core/cache/models/normalized-item.model.ts @@ -65,7 +65,7 @@ export class NormalizedItem extends NormalizedDSpaceObject { @relationship(Bundle, true) bundles: string[]; - @autoserialize + @deserialize @relationship(Relationship, true) relationships: string[]; From 9e5508f9e50943cb546000d4ff8cd7a6f14bd00e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 9 Dec 2019 14:48:55 +0100 Subject: [PATCH 46/60] 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 47/60] 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 48/60] 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 49/60] 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 50/60] 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 51/60] 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 52/60] 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 53/60] 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 54/60] 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; From 00039436b6fda02db4e0f0b0b49f04e38247ab5f Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 19 Dec 2019 13:57:44 +0100 Subject: [PATCH 55/60] more tests --- ...g-unit-input-suggestions.component.spec.ts | 64 +++++++++++++++++++ .../shared/utils/relation-query.utils.spec.ts | 18 ++++++ src/app/shared/utils/route.utils.spec.ts | 22 +++++++ 3 files changed, 104 insertions(+) create mode 100644 src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts create mode 100644 src/app/shared/utils/relation-query.utils.spec.ts create mode 100644 src/app/shared/utils/route.utils.spec.ts diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts new file mode 100644 index 0000000000..34b89cc8aa --- /dev/null +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.spec.ts @@ -0,0 +1,64 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { OrgUnitInputSuggestionsComponent } from './org-unit-input-suggestions.component'; +import { FormsModule } from '@angular/forms'; + +let component: OrgUnitInputSuggestionsComponent; +let fixture: ComponentFixture; + +let suggestions: string[]; +let testValue; + +function init() { + suggestions = ['test', 'suggestion', 'example'] + testValue = 'bla'; +} + +describe('OrgUnitInputSuggestionsComponent', () => { + beforeEach(async(() => { + init(); + TestBed.configureTestingModule({ + declarations: [OrgUnitInputSuggestionsComponent], + imports: [ + FormsModule, + ], + providers: [ + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(OrgUnitInputSuggestionsComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(OrgUnitInputSuggestionsComponent); + component = fixture.componentInstance; + component.suggestions = suggestions; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('When the component is initialized', () => { + it('should set the value to the first value of the suggestions', () => { + expect(component.value).toEqual('test'); + }); + }); + + describe('When onSubmit is called', () => { + it('should set the value to parameter of the method', () => { + component.onSubmit(testValue); + expect(component.value).toEqual(testValue); + }); + }); + + describe('When onClickSuggestion is called', () => { + it('should set the value to parameter of the method', () => { + component.onClickSuggestion(testValue); + expect(component.value).toEqual(testValue); + }); + }); + +}); diff --git a/src/app/shared/utils/relation-query.utils.spec.ts b/src/app/shared/utils/relation-query.utils.spec.ts new file mode 100644 index 0000000000..f70e904422 --- /dev/null +++ b/src/app/shared/utils/relation-query.utils.spec.ts @@ -0,0 +1,18 @@ +import { getFilterByRelation, getQueryByRelations } from './relation-query.utils'; + +describe('Relation Query Utils', () => { + const relationtype = 'isAuthorOfPublication'; + const itemUUID = 'a7939af0-36ad-430d-af09-7be8b0a4dadd'; + describe('getQueryByRelations', () => { + it('Should return the correct query based on relationtype and uuid', () => { + const result = getQueryByRelations(relationtype, itemUUID); + expect(result).toEqual('query=relation.isAuthorOfPublication:a7939af0-36ad-430d-af09-7be8b0a4dadd'); + }); + }); + describe('getFilterByRelation', () => { + it('Should return the correct query based on relationtype and uuid', () => { + const result = getFilterByRelation(relationtype, itemUUID); + expect(result).toEqual('f.isAuthorOfPublication=a7939af0-36ad-430d-af09-7be8b0a4dadd'); + }); + }); +}); diff --git a/src/app/shared/utils/route.utils.spec.ts b/src/app/shared/utils/route.utils.spec.ts new file mode 100644 index 0000000000..610fd8756d --- /dev/null +++ b/src/app/shared/utils/route.utils.spec.ts @@ -0,0 +1,22 @@ +import { currentPath } from './route.utils'; + +describe('Route Utils', () => { + const urlTree = { + root: { + children: { + primary: { + segments: [ + { path: 'test' }, + { path: 'path' } + ] + } + + } + } + }; + const router = { parseUrl: () => urlTree } as any; + it('Should return the correct current path based on the router', () => { + const result = currentPath(router); + expect(result).toEqual('/test/path'); + }); + }); From d35244384843f180235e7d8be2089aadea4ec906 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 19 Dec 2019 15:36:04 +0100 Subject: [PATCH 56/60] reverted server change --- config/environment.default.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/environment.default.js b/config/environment.default.js index df4f89a2fe..0e1e0d03bf 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -10,8 +10,9 @@ module.exports = { // The REST API server settings. rest: { ssl: true, - host: 'dspace7-entities.atmire.com', + host: 'dspace7.4science.cloud', port: 443, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/server/api' }, // Caching settings From 9d185e8a152d46600960faa7e70105f159bddf9f Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 20 Dec 2019 08:55:23 +0100 Subject: [PATCH 57/60] fixing tests --- src/app/core/data/data.service.spec.ts | 2 +- src/app/core/data/relationship.service.spec.ts | 4 ++-- src/app/core/data/relationship.service.ts | 2 +- .../object-select/item-select/item-select.component.spec.ts | 2 +- .../search-label/search-label.component.spec.ts | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 98e5f7afaa..ca5f2cc12e 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -192,7 +192,7 @@ describe('DataService', () => { dso2.self = selfLink; dso2.metadata = [{ key: 'dc.title', value: name2 }]; - spyOn(service, 'findByHref').and.returnValues(createSuccessfulRemoteDataObject$(dso)); + spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(dso)); spyOn(objectCache, 'addPatch'); }); diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts index b33db80fbe..9287935f59 100644 --- a/src/app/core/data/relationship.service.spec.ts +++ b/src/app/core/data/relationship.service.spec.ts @@ -123,8 +123,8 @@ describe('RelationshipService', () => { it('should clear the related items their cache', () => { expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self); expect(objectCache.remove).toHaveBeenCalledWith(item.self); - expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.self); - expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.uuid); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.uuid); }); }); diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index e155b1f90b..325bb59399 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -150,7 +150,7 @@ export class RelationshipService extends DataService { this.requestService.removeByHrefSubstring(item.uuid); combineLatest( this.objectCache.hasBySelfLinkObservable(item.self), - this.requestService.hasByHrefObservable(item.self) + this.requestService.hasByHrefObservable(item.uuid) ).pipe( filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC), take(1), diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 26dd55f010..08305080ca 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -16,7 +16,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { of } from 'rxjs/internal/observable/of'; -describe('ItemSelectComponent', () => { +fdescribe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts index 0b382015af..5de87be3bc 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Observable, of as observableOf } from 'rxjs'; -import { Params } from '@angular/router'; +import { Params, Router } from '@angular/router'; import { SearchLabelComponent } from './search-label.component'; import { ObjectKeysPipe } from '../../../utils/object-keys-pipe'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; @@ -39,7 +39,8 @@ describe('SearchLabelComponent', () => { declarations: [SearchLabelComponent, ObjectKeysPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, - { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() } + { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, + { provide: Router, useValue: {}} // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} } ], schemas: [NO_ERRORS_SCHEMA] From 7c39bf4b5f152e5acd3cc8a2ee7f6d6ef15324f5 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 20 Dec 2019 10:15:24 +0100 Subject: [PATCH 58/60] fixed bug where removing a filter in the submission relationship modal redirects to the homepage --- .../search-labels/search-label/search-label.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.ts b/src/app/shared/search/search-labels/search-label/search-label.component.ts index e821af19c9..956b5b81de 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.ts @@ -1,9 +1,10 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; -import { Params } from '@angular/router'; +import { Params, Router } from '@angular/router'; import { map } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../../empty.util'; import { SearchService } from '../../../../core/shared/search/search.service'; +import { currentPath } from '../../../utils/route.utils'; @Component({ selector: 'ds-search-label', @@ -25,7 +26,8 @@ export class SearchLabelComponent implements OnInit { * Initialize the instance variable */ constructor( - private searchService: SearchService) { + private searchService: SearchService, + private router: Router) { } ngOnInit(): void { @@ -55,7 +57,7 @@ export class SearchLabelComponent implements OnInit { */ private getSearchLink(): string { if (this.inPlaceSearch) { - return './'; + return currentPath(this.router); } return this.searchService.getSearchLink(); } From 01ba97af7ae561d677a34a08ec9b83df1cf5fdfb Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 20 Dec 2019 14:13:31 +0100 Subject: [PATCH 59/60] applied feedback --- config/environment.default.js | 6 +-- src/app/+search-page/search.component.html | 2 +- src/app/core/shared/operators.ts | 6 --- ...ynamic-form-control-container.component.ts | 4 +- ...okup-relation-search-tab.component.spec.ts | 10 ++-- ...p-relation-selection-tab.component.spec.ts | 49 ++++++++++++++++--- .../dspace-rest-v2/mocks/mock-response-map.ts | 5 ++ .../selectable-list-item-control.component.ts | 2 +- .../object-list/object-list.component.ts | 15 +----- .../selectable-list.actions.ts | 4 +- .../page-size-selector.component.ts | 2 +- .../search-filters.component.ts | 2 +- .../search-label.component.spec.ts | 5 +- src/app/shared/utils/route.utils.ts | 4 ++ .../sections/form/section-form.component.ts | 2 +- 15 files changed, 73 insertions(+), 45 deletions(-) diff --git a/config/environment.default.js b/config/environment.default.js index 0e1e0d03bf..24386d6cf7 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -9,11 +9,11 @@ module.exports = { }, // The REST API server settings. rest: { - ssl: true, + ssl: true, host: 'dspace7.4science.cloud', - port: 443, + port: 443, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server/api' + nameSpace: '/server/api' }, // Caching settings cache: { diff --git a/src/app/+search-page/search.component.html b/src/app/+search-page/search.component.html index a6e83d2b64..f3731607db 100644 --- a/src/app/+search-page/search.component.html +++ b/src/app/+search-page/search.component.html @@ -48,7 +48,7 @@
- +
diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 0d1aa74591..308e4f8a2d 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -125,9 +125,3 @@ export const getFirstOccurrence = () => source.pipe( map((rd) => Object.assign(rd, { payload: rd.payload.page.length > 0 ? rd.payload.page[0] : undefined })) ); - -export const obsLog = (logString?: string) => - (source: Observable): Observable => - source.pipe( - tap((t) => console.log(logString || '', t)) - ); 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 2079f23725..c85ef11e5a 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 @@ -85,8 +85,7 @@ import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/d import { getAllSucceededRemoteData, getRemoteDataPayload, - getSucceededRemoteData, - obsLog + getSucceededRemoteData } from '../../../../core/shared/operators'; import { RemoteData } from '../../../../core/data/remote-data'; import { Item } from '../../../../core/shared/item.model'; @@ -100,7 +99,6 @@ import { PaginatedList } from '../../../../core/data/paginated-list'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model'; import { MetadataValue } from '../../../../core/shared/metadata.models'; -import * as uuidv4 from 'uuid/v4'; export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type | null { switch (model.type) { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.spec.ts index 241d022dc3..4434684cbb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.spec.ts @@ -24,9 +24,11 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => { let item1; let item2; let item3; + let item4; let searchResult1; let searchResult2; let searchResult3; + let searchResult4; let listID; let selection$; @@ -39,9 +41,11 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => { item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' }); item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' }); item3 = Object.assign(new Item(), { uuid: 'c3bcbff5-ec0c-4831-8e4c-94b9c933ccac' }); + item4 = Object.assign(new Item(), { uuid: 'f96a385e-de10-45b2-be66-7f10bf52f765' }); searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 }); searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 }); searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 }); + searchResult4 = Object.assign(new ItemSearchResult(), { indexableObject: item4 }); listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3'; selection$ = observableOf([searchResult1, searchResult2]); @@ -93,12 +97,12 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => { describe('selectPage', () => { beforeEach(() => { spyOn(component.selectObject, 'emit'); - component.selectPage([searchResult1, searchResult2, searchResult3]); + component.selectPage([searchResult1, searchResult2, searchResult4]); }); it('should emit the page filtered from already selected objects and call select on the service for all objects', () => { - expect(component.selectObject.emit).toHaveBeenCalledWith(searchResult3); - expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]); + expect(component.selectObject.emit).toHaveBeenCalledWith(searchResult4); + expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult4]); }); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.spec.ts index 32c995ba94..203a4df0b0 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.spec.ts @@ -1,46 +1,59 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; -import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { VarDirective } from '../../../../../utils/var.directive'; -import { of as observableOf } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; import { Item } from '../../../../../../core/shared/item.model'; import { DsDynamicLookupRelationSelectionTabComponent } from './dynamic-lookup-relation-selection-tab.component'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; +import { Router } from '@angular/router'; +import { By } from '@angular/platform-browser'; +import { RemoteData } from '../../../../../../core/data/remote-data'; +import { PaginatedList } from '../../../../../../core/data/paginated-list'; +import { ListableObject } from '../../../../../object-collection/shared/listable-object.model'; +import { createSuccessfulRemoteDataObject$ } from '../../../../../testing/utils'; describe('DsDynamicLookupRelationSelectionTabComponent', () => { let component: DsDynamicLookupRelationSelectionTabComponent; let fixture: ComponentFixture; - let pSearchOptions = new PaginatedSearchOptions({pagination: new PaginationComponentOptions()}); + let pSearchOptions = new PaginatedSearchOptions({ pagination: new PaginationComponentOptions() }); let item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' }); let item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' }); let searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 }); let searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 }); let listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3'; - let selection$ = observableOf([searchResult1, searchResult2]); + let selection$; + let selectionRD$; + let router; function init() { - pSearchOptions = new PaginatedSearchOptions({pagination: new PaginationComponentOptions()}); + pSearchOptions = new PaginatedSearchOptions({ pagination: new PaginationComponentOptions() }); item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' }); item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' }); searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 }); searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 }); listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3'; selection$ = observableOf([searchResult1, searchResult2]); + selectionRD$ = createSelection([searchResult1, searchResult2]); + router = jasmine.createSpyObj('router', ['navigate']) } + beforeEach(async(() => { init(); TestBed.configureTestingModule({ declarations: [DsDynamicLookupRelationSelectionTabComponent, VarDirective], - imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + imports: [TranslateModule.forRoot()], providers: [ { provide: SearchConfigurationService, useValue: { paginatedSearchOptions: observableOf(pSearchOptions) - } + }, + }, + { + provide: Router, useValue: router } ], schemas: [NO_ERRORS_SCHEMA] @@ -59,4 +72,26 @@ describe('DsDynamicLookupRelationSelectionTabComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should call navigate on the router when is called resetRoute', () => { + component.resetRoute(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should call navigate on the router when is called resetRoute', () => { + component.selectionRD$ = createSelection([]); + fixture.detectChanges(); + const colComponent = fixture.debugElement.query(By.css('ds-viewable-collection')); + expect(colComponent).toBe(null); + }); + + it('should call navigate on the router when is called resetRoute', () => { + component.selectionRD$ = selectionRD$; + const colComponent = fixture.debugElement.query(By.css('ds-viewable-collection')); + expect(colComponent).not.toBe(null); + }); }); + +function createSelection(content: ListableObject[]): Observable>> { + return createSuccessfulRemoteDataObject$(new PaginatedList(undefined, content)); +} diff --git a/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts index 1d1b47ee78..a7fab782da 100644 --- a/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts +++ b/src/app/shared/mocks/dspace-rest-v2/mocks/mock-response-map.ts @@ -5,6 +5,11 @@ export class MockResponseMap extends Map {}; export const MOCK_RESPONSE_MAP: InjectionToken = new InjectionToken('mockResponseMap'); +/** + * List of endpoints with their matching mock response + * Note that this list is only used in development mode + * In production the actual endpoints on the REST server will be called + */ export const mockResponseMap: MockResponseMap = new Map([ // [ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ] ]); 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 d1536c56e6..d47e05c8fe 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 @@ -10,7 +10,7 @@ import { Observable } from 'rxjs'; templateUrl: './selectable-list-item-control.component.html' }) /** - * Component for determining what component to use depending on the item's relationship type (relationship.type) + * Component for rendering list item that has a control (checkbox or radio button) because it's selectable */ export class SelectableListItemControlComponent implements OnInit { /** diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 1be0f69106..6ca7adb3f9 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -1,21 +1,11 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - Output, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; import { RemoteData } from '../../core/data/remote-data'; import { fadeIn } from '../animations/fade'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { SearchResult } from '../search/search-result.model'; import { SelectableListService } from './selectable-list/selectable-list.service'; -import { map, take, tap } from 'rxjs/operators'; import { ViewMode } from '../../core/shared/view-mode.model'; import { Context } from '../../core/shared/context.model'; import { CollectionElementLinkType } from '../object-collection/collection-element-link.type'; @@ -60,9 +50,6 @@ export class ObjectListComponent { @Input() hidePagerWhenSinglePage = true; @Input() selectable = false; @Input() selectionConfig: { repeatable: boolean, listId: string }; - // @Input() previousSelection: ListableObject[] = []; - // allSelected = false; - // selectAllLoading = false; /** * The link type of the listable elements 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 3dedf7e6a2..010ae5609d 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 @@ -55,7 +55,7 @@ export class SelectableListSelectSingleAction extends SelectableListAction { } /** - * Action to deselect objects in a the selectable list + * Action to deselect a single object in a the selectable list */ export class SelectableListDeselectSingleAction extends SelectableListAction { payload: ListableObject; @@ -67,7 +67,7 @@ export class SelectableListDeselectSingleAction extends SelectableListAction { } /** - * Action to deselect a single object in a the selectable list + * Action to deselect objects in a the selectable list */ export class SelectableListDeselectAction extends SelectableListAction { payload: ListableObject[]; diff --git a/src/app/shared/page-size-selector/page-size-selector.component.ts b/src/app/shared/page-size-selector/page-size-selector.component.ts index 799993d35d..b200c337f8 100644 --- a/src/app/shared/page-size-selector/page-size-selector.component.ts +++ b/src/app/shared/page-size-selector/page-size-selector.component.ts @@ -14,7 +14,7 @@ import { map } from 'rxjs/operators'; }) /** - * This component represents the part of the search sidebar that contains the general search settings. + * This component represents the part of the search sidebar that contains the page size settings. */ export class PageSizeSelectorComponent implements OnInit { /** diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index e9b5f46fa8..78c40501e6 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -8,7 +8,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { SearchFilterConfig } from '../search-filter-config.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; -import { getSucceededRemoteData, obsLog } from '../../../core/shared/operators'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; import { Router } from '@angular/router'; diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts index 0b382015af..8c6860c2d3 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Observable, of as observableOf } from 'rxjs'; -import { Params } from '@angular/router'; +import { Params, Router } from '@angular/router'; import { SearchLabelComponent } from './search-label.component'; import { ObjectKeysPipe } from '../../../utils/object-keys-pipe'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; @@ -39,7 +39,8 @@ describe('SearchLabelComponent', () => { declarations: [SearchLabelComponent, ObjectKeysPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, - { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() } + { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, + { provide: Router, useValue: {} } // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/utils/route.utils.ts b/src/app/shared/utils/route.utils.ts index b0771d4f13..6510fb8894 100644 --- a/src/app/shared/utils/route.utils.ts +++ b/src/app/shared/utils/route.utils.ts @@ -1,5 +1,9 @@ import { Router } from '@angular/router'; +/** + * Util function to retrieve the current path (without query parameters) the user is on + * @param router The router service + */ export function currentPath(router: Router) { const urlTree = router.parseUrl(router.url); return '/' + urlTree.root.children.primary.segments.map((it) => it.path).join('/') diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 6c8d7c5468..49dbaea807 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -166,8 +166,8 @@ export class SubmissionSectionformComponent extends SectionModelComponent { .subscribe(([sectionData, workspaceItem]: [WorkspaceitemSectionFormObject, WorkspaceItem]) => { if (isUndefined(this.formModel)) { this.sectionData.errors = []; - // Is the first loading so init form this.workspaceItem = workspaceItem; + // Is the first loading so init form this.initForm(sectionData); this.sectionData.data = sectionData; this.subscriptions(); From 65b648b00016a1a3bd76e515e8ec9465f6ee5ca7 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 20 Dec 2019 16:48:40 +0100 Subject: [PATCH 60/60] fixed existing tests --- ...ng-metadata-list-element.component.spec.ts | 12 +++- ...xisting-metadata-list-element.component.ts | 5 +- .../item-select/item-select.component.spec.ts | 2 +- .../submission-form-collection.component.ts | 59 ++++++++++++------- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts index 7172653557..1b2f471f0b 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts @@ -1,6 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ExistingMetadataListElementComponent } from './existing-metadata-list-element.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service'; +import { Store } from '@ngrx/store'; describe('ExistingMetadataListElementComponent', () => { let component: ExistingMetadataListElementComponent; @@ -8,9 +11,14 @@ describe('ExistingMetadataListElementComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ExistingMetadataListElementComponent ] + declarations: [ExistingMetadataListElementComponent], + providers: [ + { provide: SelectableListService, useValue: {} }, + { provide: Store, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index a0de289a04..d2b519b9a3 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -15,11 +15,13 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../../app.reducer'; import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model'; +// tslint:disable:max-classes-per-file export abstract class Reorderable { constructor(public oldIndex?: number, public newIndex?: number) { } abstract getId(): string; + abstract getPlace(): number; } @@ -84,7 +86,7 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro const relationMD: MetadataValue = this.submissionItem.firstMetadata(this.relationshipOptions.metadataField, { value: this.relatedItem.uuid }); if (hasValue(relationMD)) { const metadataRepresentationMD: MetadataValue = this.submissionItem.firstMetadata(this.metadataFields, { authority: relationMD.authority }); - this.metadataRepresentation = Object.assign( + this.metadataRepresentation = Object.assign( new ItemMetadataRepresentation(metadataRepresentationMD), this.relatedItem ) @@ -107,3 +109,4 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro } } +// tslint:enable:max-classes-per-file diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 08305080ca..26dd55f010 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -16,7 +16,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { of } from 'rxjs/internal/observable/of'; -fdescribe('ItemSelectComponent', () => { +describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 88bc4904d3..f84764d6a4 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -1,8 +1,28 @@ -import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + EventEmitter, + HostListener, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges +} from '@angular/core'; import { FormControl } from '@angular/forms'; import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { debounceTime, distinctUntilChanged, filter, map, mergeMap, reduce, startWith, flatMap, find } from 'rxjs/operators'; +import { + debounceTime, + distinctUntilChanged, + filter, + find, + flatMap, + map, + mergeMap, + reduce, + startWith +} from 'rxjs/operators'; import { Collection } from '../../../core/shared/collection.model'; import { CommunityDataService } from '../../../core/data/community-data.service'; @@ -197,23 +217,21 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { find((communities: RemoteData>) => isNotEmpty(communities.payload)), mergeMap((communities: RemoteData>) => communities.payload.page)); - const listCollection$ = observableOf([]); - - // const listCollection$ = communities$.pipe( - // flatMap((communityData: Community) => { - // return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid, findOptions).pipe( - // find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), - // mergeMap((collections: RemoteData>) => collections.payload.page), - // filter((collectionData: Collection) => isNotEmpty(collectionData)), - // map((collectionData: Collection) => ({ - // communities: [{ id: communityData.id, name: communityData.name }], - // collection: { id: collectionData.id, name: collectionData.name } - // })) - // ); - // }), - // reduce((acc: any, value: any) => [...acc, ...value], []), - // startWith([]) - // ); + const listCollection$ = communities$.pipe( + flatMap((communityData: Community) => { + return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid, findOptions).pipe( + find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), + mergeMap((collections: RemoteData>) => collections.payload.page), + filter((collectionData: Collection) => isNotEmpty(collectionData)), + map((collectionData: Collection) => ({ + communities: [{ id: communityData.id, name: communityData.name }], + collection: { id: collectionData.id, name: collectionData.name } + })) + ); + }), + reduce((acc: any, value: any) => [...acc, ...value], []), + startWith([]) + ); const searchTerm$ = this.searchField.valueChanges.pipe( debounceTime(200), @@ -229,8 +247,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { } else { return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5); } - }) - ); + })); } } }