mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'reorder-name-variants' into metadata-and-relationships-combined-in-submission
This commit is contained in:
@@ -230,7 +230,7 @@
|
|||||||
"rollup-plugin-node-globals": "1.2.1",
|
"rollup-plugin-node-globals": "1.2.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.3",
|
"rollup-plugin-node-resolve": "^3.0.3",
|
||||||
"rollup-plugin-terser": "^2.0.2",
|
"rollup-plugin-terser": "^2.0.2",
|
||||||
"sass-loader": "7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"script-ext-html-webpack-plugin": "2.0.1",
|
"script-ext-html-webpack-plugin": "2.0.1",
|
||||||
"source-map": "0.7.3",
|
"source-map": "0.7.3",
|
||||||
"source-map-loader": "0.2.4",
|
"source-map-loader": "0.2.4",
|
||||||
|
3
resources/fonts/README.md
Normal file
3
resources/fonts/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Supported font formats
|
||||||
|
|
||||||
|
DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts.
|
@@ -340,6 +340,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"communityList.tabTitle": "DSpace - Community List",
|
||||||
|
|
||||||
|
"communityList.title": "List of Communities",
|
||||||
|
|
||||||
|
"communityList.showMore": "Show More",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"community.create.head": "Create a Community",
|
"community.create.head": "Create a Community",
|
||||||
|
|
||||||
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
|
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
|
||||||
@@ -821,9 +829,17 @@
|
|||||||
|
|
||||||
"item.page.person.search.title": "Articles by this author",
|
"item.page.person.search.title": "Articles by this author",
|
||||||
|
|
||||||
"item.page.related-items.view-more": "View more",
|
"item.page.related-items.view-more": "Show {{ amount }} more",
|
||||||
|
|
||||||
"item.page.related-items.view-less": "View less",
|
"item.page.related-items.view-less": "Hide last {{ amount }}",
|
||||||
|
|
||||||
|
"item.page.relationships.isAuthorOfPublication": "Publications",
|
||||||
|
|
||||||
|
"item.page.relationships.isJournalOfPublication": "Publications",
|
||||||
|
|
||||||
|
"item.page.relationships.isOrgUnitOfPerson": "Authors",
|
||||||
|
|
||||||
|
"item.page.relationships.isOrgUnitOfProject": "Research Projects",
|
||||||
|
|
||||||
"item.page.subject": "Keywords",
|
"item.page.subject": "Keywords",
|
||||||
|
|
||||||
@@ -1275,6 +1291,8 @@
|
|||||||
|
|
||||||
"project.page.titleprefix": "Research Project: ",
|
"project.page.titleprefix": "Research Project: ",
|
||||||
|
|
||||||
|
"project.search.results.head": "Project Search Results",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"publication.listelement.badge": "Publication",
|
"publication.listelement.badge": "Publication",
|
||||||
@@ -1547,6 +1565,10 @@
|
|||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
|
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
|
||||||
|
|
||||||
|
"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.selection-tab.tab-title": "Current Selection ({{ count }})",
|
"submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
|
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
|
||||||
@@ -1557,6 +1579,10 @@
|
|||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.title.Author": "Authors",
|
"submission.sections.describe.relationship-lookup.title.Author": "Authors",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.title.Funding": "Funding",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
|
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
|
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
|
||||||
|
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
|||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
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 { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
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
|
* The current pagination configuration for the page used by the FindAll method
|
||||||
* Currently simply renders all bitstream formats
|
* Currently simply renders all bitstream formats
|
||||||
*/
|
*/
|
||||||
config: FindAllOptions = Object.assign(new FindAllOptions(), {
|
config: FindListOptions = Object.assign(new FindListOptions(), {
|
||||||
elementsPerPage: 20
|
elementsPerPage: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit {
|
|||||||
* @param event The page change event
|
* @param event The page change event
|
||||||
*/
|
*/
|
||||||
onPageChange(event) {
|
onPageChange(event) {
|
||||||
this.config = Object.assign(new FindAllOptions(), this.config, {
|
this.config = Object.assign(new FindListOptions(), this.config, {
|
||||||
currentPage: event,
|
currentPage: event,
|
||||||
});
|
});
|
||||||
this.pageConfig.currentPage = event;
|
this.pageConfig.currentPage = event;
|
||||||
|
@@ -26,7 +26,9 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent
|
|||||||
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
||||||
import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.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';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -55,7 +57,9 @@ import { StatisticsModule } from '../statistics/statistics.module';
|
|||||||
ItemComponent,
|
ItemComponent,
|
||||||
GenericItemPageFieldComponent,
|
GenericItemPageFieldComponent,
|
||||||
MetadataRepresentationListComponent,
|
MetadataRepresentationListComponent,
|
||||||
RelatedEntitiesSearchComponent
|
RelatedEntitiesSearchComponent,
|
||||||
|
TabbedRelatedEntitiesSearchComponent,
|
||||||
|
AbstractIncrementalListComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ItemComponent,
|
ItemComponent,
|
||||||
@@ -65,7 +69,8 @@ import { StatisticsModule } from '../statistics/statistics.module';
|
|||||||
RelatedEntitiesSearchComponent,
|
RelatedEntitiesSearchComponent,
|
||||||
RelatedItemsComponent,
|
RelatedItemsComponent,
|
||||||
MetadataRepresentationListComponent,
|
MetadataRepresentationListComponent,
|
||||||
ItemPageTitleFieldComponent
|
ItemPageTitleFieldComponent,
|
||||||
|
TabbedRelatedEntitiesSearchComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
PublicationComponent
|
PublicationComponent
|
||||||
|
@@ -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<T> 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,20 @@
|
|||||||
<ds-metadata-field-wrapper *ngIf="representations$ && (representations$ | async)?.length > 0" [label]="label">
|
<ds-metadata-field-wrapper [label]="label">
|
||||||
<ds-metadata-representation-loader *ngFor="let rep of (representations$ | async)"
|
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||||
[mdRepresentation]="rep">
|
<ng-container *ngVar="(objectPage | async) as representations">
|
||||||
</ds-metadata-representation-loader>
|
<ds-metadata-representation-loader *ngFor="let rep of representations"
|
||||||
<div *ngIf="(representations$ | async)?.length < total" class="mt-2">
|
[mdRepresentation]="rep">
|
||||||
<a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
|
</ds-metadata-representation-loader>
|
||||||
</div>
|
<ds-loading *ngIf="(i + 1) === objects.length && (i > 0) && (!representations || representations?.length === 0)" message="{{'loading.default' | translate}}"></ds-loading>
|
||||||
<div *ngIf="limit > originalLimit" class="mt-2">
|
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && representations?.length > 0">
|
||||||
<a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
|
<div *ngIf="(objects.length * incrementBy) < total" class="float-left">
|
||||||
</div>
|
<a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
|
||||||
|
translate:{ amount: (total - (objects.length * incrementBy) < incrementBy) ? total - (objects.length * incrementBy) : incrementBy } }}</a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="objects.length > 1" class="float-right">
|
||||||
|
<a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
|
||||||
|
translate:{ amount: representations?.length } }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -7,6 +7,8 @@ import { Item } from '../../../core/shared/item.model';
|
|||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
const itemType = 'Person';
|
const itemType = 'Person';
|
||||||
const metadataField = 'dc.contributor.author';
|
const metadataField = 'dc.contributor.author';
|
||||||
@@ -64,7 +66,7 @@ describe('MetadataRepresentationListComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [MetadataRepresentationListComponent],
|
declarations: [MetadataRepresentationListComponent, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RelationshipService, useValue: relationshipService }
|
{ provide: RelationshipService, useValue: relationshipService }
|
||||||
],
|
],
|
||||||
@@ -88,33 +90,29 @@ describe('MetadataRepresentationListComponent', () => {
|
|||||||
expect(fields.length).toBe(2);
|
expect(fields.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize the original limit', () => {
|
it('should contain one page of items', () => {
|
||||||
expect(comp.originalLimit).toEqual(comp.limit);
|
expect(comp.objects.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when viewMore is called', () => {
|
describe('when increase is called', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.viewMore();
|
comp.increase();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the limit to a high number in order to retrieve all metadata representations', () => {
|
it('should add a new page to the list', () => {
|
||||||
expect(comp.limit).toBeGreaterThanOrEqual(999);
|
expect(comp.objects.length).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when viewLess is called', () => {
|
describe('when decrease is called', () => {
|
||||||
let originalLimit;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Store the original value of limit
|
// Add a second page
|
||||||
originalLimit = comp.limit;
|
comp.objects.push(observableOf(undefined));
|
||||||
// Set limit to a random number
|
comp.decrease();
|
||||||
comp.limit = 458;
|
|
||||||
comp.viewLess();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset the limit to the original value', () => {
|
it('should decrease the list of pages', () => {
|
||||||
expect(comp.limit).toEqual(originalLimit);
|
expect(comp.objects.length).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
|
||||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { filter, map, switchMap } from 'rxjs/operators';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
import { map, filter } from 'rxjs/operators';
|
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-metadata-representation-list',
|
selector: 'ds-metadata-representation-list',
|
||||||
@@ -22,7 +22,7 @@ import { map, filter } from 'rxjs/operators';
|
|||||||
* It expects an itemType to resolve the metadata to a an item
|
* It expects an itemType to resolve the metadata to a an item
|
||||||
* It expects a label to put on top of the list
|
* It expects a label to put on top of the list
|
||||||
*/
|
*/
|
||||||
export class MetadataRepresentationListComponent implements OnInit {
|
export class MetadataRepresentationListComponent extends AbstractIncrementalListComponent<Observable<MetadataRepresentation[]>> {
|
||||||
/**
|
/**
|
||||||
* The parent of the list of related items to display
|
* The parent of the list of related items to display
|
||||||
*/
|
*/
|
||||||
@@ -44,22 +44,11 @@ export class MetadataRepresentationListComponent implements OnInit {
|
|||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The max amount of representations to display
|
* The amount to increment the list by when clicking "view more"
|
||||||
* Defaults to 10
|
* Defaults to 10
|
||||||
* The default can optionally be overridden by providing the limit as input to the component
|
* The default can optionally be overridden by providing the limit as input to the component
|
||||||
*/
|
*/
|
||||||
@Input() limit = 10;
|
@Input() incrementBy = 10;
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of metadata-representations to display
|
|
||||||
*/
|
|
||||||
representations$: Observable<MetadataRepresentation[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* The total amount of metadata values available
|
||||||
@@ -67,30 +56,28 @@ export class MetadataRepresentationListComponent implements OnInit {
|
|||||||
total: number;
|
total: number;
|
||||||
|
|
||||||
constructor(public relationshipService: RelationshipService) {
|
constructor(public relationshipService: RelationshipService) {
|
||||||
}
|
super();
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.originalLimit = this.limit;
|
|
||||||
this.setRepresentations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the metadata representations
|
* Get a specific page
|
||||||
|
* @param page The page to fetch
|
||||||
*/
|
*/
|
||||||
setRepresentations() {
|
getPage(page: number): Observable<MetadataRepresentation[]> {
|
||||||
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
|
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
|
||||||
this.total = metadata.length;
|
this.total = metadata.length;
|
||||||
this.representations$ = this.resolveMetadataRepresentations(metadata);
|
return this.resolveMetadataRepresentations(metadata, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a list of metadata values to a list of metadata representations
|
* 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<MetadataRepresentation[]> {
|
resolveMetadataRepresentations(metadata: MetadataValue[], page: number): Observable<MetadataRepresentation[]> {
|
||||||
return observableZip(
|
return observableZip(
|
||||||
...metadata
|
...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: any) => Object.assign(new MetadataValue(), metadatum))
|
||||||
.map((metadatum: MetadataValue) => {
|
.map((metadatum: MetadataValue) => {
|
||||||
if (metadatum.isVirtual) {
|
if (metadatum.isVirtual) {
|
||||||
@@ -115,20 +102,4 @@ export class MetadataRepresentationListComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand the list to display all metadata representations
|
|
||||||
*/
|
|
||||||
viewMore() {
|
|
||||||
this.limit = 9999;
|
|
||||||
this.setRepresentations();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collapse the list to display the originally displayed metadata representations
|
|
||||||
*/
|
|
||||||
viewLess() {
|
|
||||||
this.limit = this.originalLimit;
|
|
||||||
this.setRepresentations();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<ds-filtered-search-page
|
<ds-configuration-search-page
|
||||||
[fixedFilterQuery]="fixedFilter"
|
[fixedFilterQuery]="fixedFilter"
|
||||||
|
[configuration]="configuration"
|
||||||
[configuration$]="configuration$"
|
[configuration$]="configuration$"
|
||||||
[searchEnabled]="searchEnabled"
|
[searchEnabled]="searchEnabled"
|
||||||
[sideBarWidth]="sideBarWidth">
|
[sideBarWidth]="sideBarWidth">
|
||||||
</ds-filtered-search-page>
|
</ds-configuration-search-page>
|
||||||
|
@@ -14,7 +14,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
id: 'id1'
|
id: 'id1'
|
||||||
});
|
});
|
||||||
const mockRelationType = 'publicationsOfAuthor';
|
const mockRelationType = 'publicationsOfAuthor';
|
||||||
const mockRelationEntityType = 'publication';
|
const mockConfiguration = 'publication';
|
||||||
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
@@ -30,7 +30,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.relationType = mockRelationType;
|
comp.relationType = mockRelationType;
|
||||||
comp.item = mockItem;
|
comp.item = mockItem;
|
||||||
comp.relationEntityType = mockRelationEntityType;
|
comp.configuration = mockConfiguration;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
|
|
||||||
it('should create a configuration$', () => {
|
it('should create a configuration$', () => {
|
||||||
comp.configuration$.subscribe((configuration) => {
|
comp.configuration$.subscribe((configuration) => {
|
||||||
expect(configuration).toEqual(mockRelationEntityType);
|
expect(configuration).toEqual(mockConfiguration);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() relationType: string;
|
@Input() relationType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional configuration to use for the search options
|
||||||
|
*/
|
||||||
|
@Input() configuration: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item to render relationships for
|
* The item to render relationships for
|
||||||
*/
|
*/
|
||||||
@Input() item: Item;
|
@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)
|
* Whether or not the search bar and title should be displayed (defaults to true)
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -53,8 +51,8 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
|
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
|
||||||
this.fixedFilter = getFilterByRelation(this.relationType, this.item.id);
|
this.fixedFilter = getFilterByRelation(this.relationType, this.item.id);
|
||||||
}
|
}
|
||||||
if (isNotEmpty(this.relationEntityType)) {
|
if (isNotEmpty(this.configuration)) {
|
||||||
this.configuration$ = of(this.relationEntityType);
|
this.configuration$ = of(this.configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
|
||||||
|
<ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<div class="mt-4">
|
||||||
|
<ds-related-entities-search [item]="item"
|
||||||
|
[relationType]="relationType.filter"
|
||||||
|
[configuration]="relationType.configuration"
|
||||||
|
[searchEnabled]="searchEnabled"
|
||||||
|
[sideBarWidth]="sideBarWidth">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
</ngb-tabset>
|
||||||
|
<div *ngIf="relationTypes.length === 1" class="mt-4">
|
||||||
|
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
|
||||||
|
[relationType]="relationType.filter"
|
||||||
|
[configuration]="relationType.configuration"
|
||||||
|
[searchEnabled]="searchEnabled"
|
||||||
|
[sideBarWidth]="sideBarWidth">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { TabbedRelatedEntitiesSearchComponent } from './tabbed-related-entities-search.component';
|
||||||
|
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;
|
||||||
|
let fixture: ComponentFixture<TabbedRelatedEntitiesSearchComponent>;
|
||||||
|
|
||||||
|
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: {
|
||||||
|
queryParams: observableOf({ 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', () => {
|
||||||
|
comp.activeTab$.subscribe((activeTab) => {
|
||||||
|
expect(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'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,76 @@
|
|||||||
|
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',
|
||||||
|
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 implements OnInit {
|
||||||
|
/**
|
||||||
|
* The types of relationships to fetch items for
|
||||||
|
* e.g. 'isAuthorOfPublication'
|
||||||
|
*/
|
||||||
|
@Input() relationTypes: Array<{
|
||||||
|
label: string,
|
||||||
|
filter: string,
|
||||||
|
configuration?: 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active tab
|
||||||
|
*/
|
||||||
|
activeTab$: Observable<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.queryParams.pipe(
|
||||||
|
map((params) => params.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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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 { Item } from '../../../core/shared/item.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { FindAllOptions } from '../../../core/data/request.models';
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-related-items',
|
selector: 'ds-related-items',
|
||||||
@@ -17,7 +17,7 @@ import { Subscription } from 'rxjs';
|
|||||||
* This component is used for displaying relations between items
|
* 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
|
* 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<Observable<RemoteData<PaginatedList<Item>>>> {
|
||||||
/**
|
/**
|
||||||
* The parent of the list of related items to display
|
* The parent of the list of related items to display
|
||||||
*/
|
*/
|
||||||
@@ -30,79 +30,38 @@ export class RelatedItemsComponent implements OnInit, OnDestroy {
|
|||||||
@Input() relationType: string;
|
@Input() relationType: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default options to start a search request with
|
* The amount to increment the list by when clicking "view more"
|
||||||
* Optional input, should you wish a different page size (or other options)
|
* 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() incrementBy = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options to start a search request with
|
||||||
|
* Optional input
|
||||||
|
*/
|
||||||
|
@Input() options = new FindListOptions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An i18n label to use as a title for the list (usually describes the relation)
|
* An i18n label to use as a title for the list (usually describes the relation)
|
||||||
*/
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Completely hide the component until there's at least one item visible
|
|
||||||
*/
|
|
||||||
@HostBinding('class.d-none') hidden = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of related items
|
|
||||||
*/
|
|
||||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search options for displaying all elements in a list
|
|
||||||
*/
|
|
||||||
allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 });
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view-mode we're currently on
|
* The view-mode we're currently on
|
||||||
* @type {ViewMode}
|
* @type {ViewMode}
|
||||||
*/
|
*/
|
||||||
viewMode = ViewMode.ListElement;
|
viewMode = ViewMode.ListElement;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the list is currently expanded to show all related items
|
|
||||||
*/
|
|
||||||
showingAll = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription on items used to update the "hidden" property of this component
|
|
||||||
*/
|
|
||||||
itemSub: Subscription;
|
|
||||||
|
|
||||||
constructor(public relationshipService: RelationshipService) {
|
constructor(public relationshipService: RelationshipService) {
|
||||||
}
|
super();
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
|
|
||||||
this.itemSub = this.items$.subscribe((itemsRD: RemoteData<PaginatedList<Item>>) => {
|
|
||||||
this.hidden = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand the list to display all related items
|
* Get a specific page
|
||||||
|
* @param page The page to fetch
|
||||||
*/
|
*/
|
||||||
viewMore() {
|
getPage(page: number): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.allOptions);
|
return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.incrementBy, currentPage: page }));
|
||||||
this.showingAll = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collapse the list to display the originally displayed items
|
|
||||||
*/
|
|
||||||
viewLess() {
|
|
||||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
|
|
||||||
this.showingAll = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from the item subscription
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.itemSub) {
|
|
||||||
this.itemSub.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,20 @@
|
|||||||
<ds-metadata-field-wrapper *ngIf="(items$ | async)?.payload?.page?.length > 0" [label]="label">
|
<ds-metadata-field-wrapper [label]="label">
|
||||||
<ds-listable-object-component-loader *ngFor="let item of (items$ | async)?.payload?.page"
|
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||||
[object]="item" [viewMode]="viewMode">
|
<ng-container *ngVar="(objectPage | async) as itemsRD">
|
||||||
</ds-listable-object-component-loader>
|
<ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page"
|
||||||
<div *ngIf="(items$ | async)?.payload?.page?.length < (items$ | async)?.payload?.totalElements" class="mt-2" id="view-more">
|
[object]="item" [viewMode]="viewMode">
|
||||||
<a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
|
</ds-listable-object-component-loader>
|
||||||
</div>
|
<ds-loading *ngIf="(i + 1) === objects.length && (itemsRD || i > 0) && !(itemsRD?.hasSucceeded && itemsRD?.payload && itemsRD?.payload?.page?.length > 0)" message="{{'loading.default' | translate}}"></ds-loading>
|
||||||
<div *ngIf="showingAll" class="mt-2" id="view-less">
|
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && itemsRD?.payload?.page?.length > 0">
|
||||||
<a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
|
<div *ngIf="itemsRD?.payload?.totalPages > objects.length" class="float-left" id="view-more">
|
||||||
</div>
|
<a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
|
||||||
|
translate:{ amount: (itemsRD?.payload?.totalElements - (incrementBy * objects.length) < incrementBy) ? itemsRD?.payload?.totalElements - (incrementBy * objects.length) : incrementBy } }}</a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="objects.length > 1" class="float-right" id="view-less">
|
||||||
|
<a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
|
||||||
|
translate:{ amount: itemsRD?.payload?.page?.length } }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -9,6 +9,8 @@ import { createRelationshipsObservable } from '../item-types/shared/item.compone
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
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(), {
|
const parentItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
@@ -42,7 +44,7 @@ describe('RelatedItemsComponent', () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [RelatedItemsComponent],
|
declarations: [RelatedItemsComponent, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RelationshipService, useValue: relationshipService }
|
{ provide: RelationshipService, useValue: relationshipService }
|
||||||
],
|
],
|
||||||
@@ -65,31 +67,33 @@ describe('RelatedItemsComponent', () => {
|
|||||||
expect(fields.length).toBe(mockItems.length);
|
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(() => {
|
beforeEach(() => {
|
||||||
comp.viewMore();
|
comp.increase();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
|
it('should add a new page to the list', () => {
|
||||||
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.allOptions);
|
expect(comp.objects.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set showingAll to true', () => {
|
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => {
|
||||||
expect(comp.showingAll).toEqual(true);
|
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.incrementBy, currentPage: 2 }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when viewLess is called', () => {
|
describe('when decrease is called', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.viewLess();
|
// Add a second page
|
||||||
|
comp.objects.push(observableOf(undefined));
|
||||||
|
comp.decrease();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
|
it('should decrease the list of pages', () => {
|
||||||
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.options);
|
expect(comp.objects.length).toEqual(1);
|
||||||
});
|
|
||||||
|
|
||||||
it('should set showingAll to false', () => {
|
|
||||||
expect(comp.showingAll).toEqual(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -34,6 +34,12 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements
|
|||||||
*/
|
*/
|
||||||
@Input() configuration: string;
|
@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,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SidebarService,
|
protected sidebarService: SidebarService,
|
||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { configureSearchComponentTestingModule } from './search.component.spec';
|
|
||||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
|
||||||
|
|
||||||
describe('FilteredSearchPageComponent', () => {
|
|
||||||
let comp: FilteredSearchPageComponent;
|
|
||||||
let fixture: ComponentFixture<FilteredSearchPageComponent>;
|
|
||||||
let searchConfigService: SearchConfigurationService;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
configureSearchComponentTestingModule(FilteredSearchPageComponent);
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(FilteredSearchPageComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
searchConfigService = (comp as any).searchConfigService;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,61 +0,0 @@
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
|
||||||
import { SearchComponent } from './search.component';
|
|
||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
|
||||||
import { pushInOut } from '../shared/animations/push';
|
|
||||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { hasValue } from '../shared/empty.util';
|
|
||||||
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.component.scss'],
|
|
||||||
templateUrl: './search.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
animations: [pushInOut],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
|
||||||
useClass: SearchConfigurationService
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
export class FilteredSearchPageComponent extends SearchComponent implements OnInit {
|
|
||||||
/**
|
|
||||||
* The actual query for the fixed filter.
|
|
||||||
* If empty, the query will be determined by the route parameter called 'fixedFilterQuery'
|
|
||||||
*/
|
|
||||||
@Input() fixedFilterQuery: string;
|
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
|
||||||
protected sidebarService: SidebarService,
|
|
||||||
protected windowService: HostWindowService,
|
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
|
||||||
protected routeService: RouteService,
|
|
||||||
protected router: Router) {
|
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
if (hasValue(this.fixedFilterQuery)) {
|
|
||||||
this.routeService.setParameter('fixedFilterQuery', this.fixedFilterQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { SearchComponent } from './search.component';
|
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
import { SearchPageComponent } from './search-page.component';
|
import { SearchPageComponent } from './search-page.component';
|
||||||
|
@@ -6,8 +6,6 @@ import { SearchPageRoutingModule } from './search-page-routing.module';
|
|||||||
import { SearchPageComponent } from './search-page.component';
|
import { SearchPageComponent } from './search-page.component';
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
|
||||||
import { SearchComponent } from './search.component';
|
import { SearchComponent } from './search.component';
|
||||||
import { SearchTrackerComponent } from './search-tracker.component';
|
import { SearchTrackerComponent } from './search-tracker.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
@@ -15,7 +13,6 @@ import { StatisticsModule } from '../statistics/statistics.module';
|
|||||||
const components = [
|
const components = [
|
||||||
SearchPageComponent,
|
SearchPageComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
FilteredSearchPageComponent,
|
|
||||||
ConfigurationSearchPageComponent,
|
ConfigurationSearchPageComponent,
|
||||||
SearchTrackerComponent,
|
SearchTrackerComponent,
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
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 { startWith, switchMap, } from 'rxjs/operators';
|
||||||
import { PaginatedList } from '../core/data/paginated-list';
|
import { PaginatedList } from '../core/data/paginated-list';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
@@ -27,6 +27,7 @@ export function getAdminModulePath() {
|
|||||||
RouterModule.forRoot([
|
RouterModule.forRoot([
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
{ 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: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
|
||||||
{ path: 'handle', 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' },
|
{ path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { StoreEffects } from './store.effects';
|
import { StoreEffects } from './store.effects';
|
||||||
import { NotificationsEffects } from './shared/notifications/notifications.effects';
|
import { NotificationsEffects } from './shared/notifications/notifications.effects';
|
||||||
import { NavbarEffects } from './navbar/navbar.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 { 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 = [
|
export const appEffects = [
|
||||||
StoreEffects,
|
StoreEffects,
|
||||||
|
@@ -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 { 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 { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
||||||
import { NavbarModule } from './navbar/navbar.module';
|
import { NavbarModule } from './navbar/navbar.module';
|
||||||
|
import { ClientCookieService } from './core/services/client-cookie.service';
|
||||||
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
|
||||||
import { ClientCookieService } from './core/services/client-cookie.service';
|
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
return ENV_CONFIG;
|
return ENV_CONFIG;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||||
|
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
||||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||||
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||||
@@ -34,6 +35,7 @@ export interface AppState {
|
|||||||
objectSelection: ObjectSelectionListState;
|
objectSelection: ObjectSelectionListState;
|
||||||
selectableLists: SelectableListsState;
|
selectableLists: SelectableListsState;
|
||||||
relationshipLists: NameVariantListsState;
|
relationshipLists: NameVariantListsState;
|
||||||
|
communityList: CommunityListState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -52,7 +54,8 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
menus: menusReducer,
|
menus: menusReducer,
|
||||||
objectSelection: objectSelectionReducer,
|
objectSelection: objectSelectionReducer,
|
||||||
selectableLists: selectableListReducer,
|
selectableLists: selectableListReducer,
|
||||||
relationshipLists: nameVariantReducer
|
relationshipLists: nameVariantReducer,
|
||||||
|
communityList: CommunityListReducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerStateSelector = (state: AppState) => state.router;
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
40
src/app/community-list-page/community-list-datasource.ts
Normal file
40
src/app/community-list-page/community-list-datasource.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<FlatNode> {
|
||||||
|
|
||||||
|
private communityList$ = new BehaviorSubject<FlatNode[]>([]);
|
||||||
|
public loading$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
constructor(private communityListService: CommunityListService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h2>{{ 'communityList.title' | translate }}</h2>
|
||||||
|
<ds-community-list></ds-community-list>
|
||||||
|
</div>
|
@@ -0,0 +1,41 @@
|
|||||||
|
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;
|
||||||
|
let fixture: ComponentFixture<CommunityListPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [CommunityListPageComponent],
|
||||||
|
providers: [
|
||||||
|
CommunityListPageComponent,
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityListPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', inject([CommunityListPageComponent], (comp: CommunityListPageComponent) => {
|
||||||
|
expect(comp).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
13
src/app/community-list-page/community-list-page.component.ts
Normal file
13
src/app/community-list-page/community-list-page.component.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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',
|
||||||
|
})
|
||||||
|
export class CommunityListPageComponent {
|
||||||
|
|
||||||
|
}
|
26
src/app/community-list-page/community-list-page.module.ts
Normal file
26
src/app/community-list-page/community-list-page.module.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page which houses a title and the community list, as described in community-list.component
|
||||||
|
*/
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
CommunityListPageRoutingModule,
|
||||||
|
CdkTreeModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CommunityListPageComponent,
|
||||||
|
CommunityListComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CommunityListPageModule {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
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 './community-list-service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RouterModule to help navigate to the page with the community list tree
|
||||||
|
*/
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CommunityListPageComponent,
|
||||||
|
pathMatch: 'full',
|
||||||
|
data: { title: 'communityList.tabTitle' }
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
CdkTreeModule,
|
||||||
|
],
|
||||||
|
providers: [CommunityListService]
|
||||||
|
})
|
||||||
|
export class CommunityListPageRoutingModule {
|
||||||
|
}
|
574
src/app/community-list-page/community-list-service.spec.ts
Normal file
574
src/app/community-list-page/community-list-service.spec.ts
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
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';
|
||||||
|
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 { take } from 'rxjs/operators';
|
||||||
|
import { FindListOptions } from '../core/data/request.models';
|
||||||
|
|
||||||
|
describe('CommunityListService', () => {
|
||||||
|
let store: MockStore<AppState>;
|
||||||
|
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 },
|
||||||
|
{ provide: Store, useValue: MockStore },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
store = TestBed.get(Store);
|
||||||
|
service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store);
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store));
|
||||||
|
|
||||||
|
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, observableOf(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], observableOf(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], observableOf(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, observableOf(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, observableOf(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, observableOf(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' }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
service.getIsExpandable(communityWithSubcoms).pipe(take(1)).subscribe((result) => {
|
||||||
|
expect(result).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' }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
service.getIsExpandable(communityWithCollections).pipe(take(1)).subscribe((result) => {
|
||||||
|
expect(result).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' }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
service.getIsExpandable(communityWithNoSubcomsOrColls).pipe(take(1)).subscribe((result) => {
|
||||||
|
expect(result).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
335
src/app/community-list-page/community-list-service.ts
Normal file
335
src/app/community-list-page/community-list-service.ts
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
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<boolean>;
|
||||||
|
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<FlatNode[]>>): Observable<FlatNode[]> =>
|
||||||
|
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<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),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a show More flatnode where only the level and parent are of importance
|
||||||
|
*/
|
||||||
|
export const showMoreFlatNode = (
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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<Observable<PaginatedList<Community>>>;
|
||||||
|
|
||||||
|
topCommunitiesConfig: PaginationComponentOptions;
|
||||||
|
topCommunitiesSortConfig: SortOptions;
|
||||||
|
|
||||||
|
maxSubCommunitiesPerPage: number;
|
||||||
|
maxCollectionsPerPage: number;
|
||||||
|
|
||||||
|
constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService,
|
||||||
|
private store: Store<any>) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void {
|
||||||
|
this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpandedNodesFromStore(): Observable<FlatNode[]> {
|
||||||
|
return this.store.select(expandedNodesSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoadingNodeFromStore(): Observable<FlatNode> {
|
||||||
|
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<FlatNode[]> {
|
||||||
|
const res = this.payloads$.map((payload) => {
|
||||||
|
return payload.pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((result: PaginatedList<Community>) => {
|
||||||
|
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<Community>,
|
||||||
|
level: number,
|
||||||
|
parent: FlatNode,
|
||||||
|
expandedNodes: FlatNode[]): Observable<FlatNode[]> {
|
||||||
|
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<FlatNode[]> {
|
||||||
|
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<PaginatedList<Community>>) => rd.hasSucceeded),
|
||||||
|
take(1),
|
||||||
|
switchMap((rd: RemoteData<PaginatedList<Community>>) =>
|
||||||
|
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<PaginatedList<Collection>>) => rd.hasSucceeded),
|
||||||
|
take(1),
|
||||||
|
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
||||||
|
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<boolean> {
|
||||||
|
let hasSubcoms$: Observable<boolean>;
|
||||||
|
let hasColls$: Observable<boolean>;
|
||||||
|
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||||
|
.pipe(
|
||||||
|
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
|
||||||
|
take(1),
|
||||||
|
map((results) => results.payload.totalElements > 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
||||||
|
.pipe(
|
||||||
|
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
|
||||||
|
take(1),
|
||||||
|
map((results) => results.payload.totalElements > 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
let hasChildren$: Observable<boolean>;
|
||||||
|
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
|
||||||
|
take(1),
|
||||||
|
map((result: [boolean]) => {
|
||||||
|
if (result[0] || result[1]) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasChildren$;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
src/app/community-list-page/community-list.actions.ts
Normal file
35
src/app/community-list-page/community-list.actions.ts
Normal file
@@ -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;
|
45
src/app/community-list-page/community-list.reducer.spec.ts
Normal file
45
src/app/community-list-page/community-list.reducer.spec.ts
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
36
src/app/community-list-page/community-list.reducer.ts
Normal file
36
src/app/community-list-page/community-list.reducer.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { FlatNode } from './community-list-service';
|
||||||
|
import { CommunityListActions, 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: CommunityListActions) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,91 @@
|
|||||||
|
<ds-loading *ngIf="(dataSource.loading$ | async) && loadingNode === undefined " class="ds-loading"></ds-loading>
|
||||||
|
|
||||||
|
<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
|
||||||
|
<!-- This is the tree node template for show more node -->
|
||||||
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
|
||||||
|
class="example-tree-node show-more-node">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default" cdkTreeNodeToggle>
|
||||||
|
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<div class="align-middle pt-2">
|
||||||
|
<a *ngIf="node!==loadingNode" [routerLink]="" (click)="getNextPage(node)"
|
||||||
|
class="btn btn-outline-secondary btn-sm">
|
||||||
|
{{ 'communityList.showMore' | translate }}
|
||||||
|
</a>
|
||||||
|
<ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-loading"></ds-loading>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
|
<div class="d-flex">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cdk-tree-node>
|
||||||
|
<!-- This is the tree node template for expandable nodes (coms and subcoms with children) -->
|
||||||
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
|
||||||
|
class="example-tree-node expandable-node">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default" cdkTreeNodeToggle
|
||||||
|
[attr.aria-label]="'toggle ' + node.name"
|
||||||
|
(click)="toggleExpanded(node)"
|
||||||
|
[ngClass]="(node.isExpandable$ | async) ? 'visible' : 'invisible'">
|
||||||
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<h5 class="align-middle pt-2">
|
||||||
|
<a [routerLink]="node.route" class="lead">
|
||||||
|
{{node.name}}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<ds-truncatable [id]="node.id">
|
||||||
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
|
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
||||||
|
<button type="button" class="btn btn-default invisible">
|
||||||
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
||||||
|
<span>{{node.payload.shortDescription}}</span>
|
||||||
|
</ds-truncatable-part>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ds-truncatable>
|
||||||
|
<div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async"
|
||||||
|
cdkTreeNodePadding>
|
||||||
|
<button type="button" class="btn btn-default invisible">
|
||||||
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<ds-loading class="ds-loading"></ds-loading>
|
||||||
|
</div>
|
||||||
|
</cdk-tree-node>
|
||||||
|
<!-- This is the tree node template for leaf nodes (collections and (sub)coms without children) -->
|
||||||
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding
|
||||||
|
class="example-tree-node childless-node">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default" cdkTreeNodeToggle>
|
||||||
|
<span class="fa fa-chevron-right invisible"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<h6 class="align-middle pt-2">
|
||||||
|
<a [routerLink]="node.route" class="lead">
|
||||||
|
{{node.name}}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<ds-truncatable [id]="node.id">
|
||||||
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
|
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
||||||
|
<button type="button" class="btn btn-default invisible">
|
||||||
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
||||||
|
<span>{{node.payload.shortDescription}}</span>
|
||||||
|
</ds-truncatable-part>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ds-truncatable>
|
||||||
|
</cdk-tree-node>
|
||||||
|
</cdk-tree>
|
@@ -0,0 +1,336 @@
|
|||||||
|
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 { 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<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',
|
||||||
|
})
|
||||||
|
];
|
||||||
|
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',
|
||||||
|
}), observableOf(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',
|
||||||
|
}), observableOf(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',
|
||||||
|
}), observableOf(false), 0, false, null
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let communityListServiceStub;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
communityListServiceStub = {
|
||||||
|
topPageSize: 2,
|
||||||
|
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;
|
||||||
|
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, observableOf(false), topNode.level + 1, false, topNode)];
|
||||||
|
});
|
||||||
|
const possibleColls: Collection[] = matchingTopComWithArrays.collections;
|
||||||
|
let collFlatnodes = [];
|
||||||
|
possibleColls.map((coll: Collection) => {
|
||||||
|
collFlatnodes = [...collFlatnodes, toFlatNode(coll, observableOf(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(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,104 @@
|
|||||||
|
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, OnDestroy {
|
||||||
|
|
||||||
|
private expandedNodes: FlatNode[] = [];
|
||||||
|
public loadingNode: FlatNode;
|
||||||
|
|
||||||
|
treeControl = new FlatTreeControl<FlatNode>(
|
||||||
|
(node) => node.level, (node) => true
|
||||||
|
);
|
||||||
|
|
||||||
|
dataSource: CommunityListDatasource;
|
||||||
|
|
||||||
|
constructor(private communityListService: CommunityListService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.dataSource = new CommunityListDatasource(this.communityListService);
|
||||||
|
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)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.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 {
|
||||||
|
this.communityListService.getNextPageTopCommunities();
|
||||||
|
this.dataSource.loadCommunities(this.expandedNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -65,7 +65,7 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
|
|||||||
@relationship(Bundle, true)
|
@relationship(Bundle, true)
|
||||||
bundles: string[];
|
bundles: string[];
|
||||||
|
|
||||||
@autoserialize
|
@deserialize
|
||||||
@relationship(Relationship, true)
|
@relationship(Relationship, true)
|
||||||
relationships: string[];
|
relationships: string[];
|
||||||
|
|
||||||
|
@@ -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 {
|
export class SearchParam {
|
||||||
constructor(public fieldName: string, public fieldValue: any) {
|
constructor(public fieldName: string, public fieldValue: any) {
|
||||||
|
@@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing';
|
|||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
import { RequestService } from '../data/request.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 { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ describe('ConfigService', () => {
|
|||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let halService: any;
|
let halService: any;
|
||||||
|
|
||||||
const findOptions: FindAllOptions = new FindAllOptions();
|
const findOptions: FindListOptions = new FindListOptions();
|
||||||
|
|
||||||
const scopeName = 'traditional';
|
const scopeName = 'traditional';
|
||||||
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
||||||
|
@@ -2,7 +2,7 @@ import { merge as observableMerge, Observable, throwError as observableThrowErro
|
|||||||
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { ConfigSuccessResponse } from '../cache/response.models';
|
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 { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { ConfigData } from './config-data';
|
import { ConfigData } from './config-data';
|
||||||
@@ -35,7 +35,7 @@ export abstract class ConfigService {
|
|||||||
return `${endpoint}/${resourceName}`;
|
return `${endpoint}/${resourceName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getConfigSearchHref(endpoint, options: FindAllOptions = {}): string {
|
protected getConfigSearchHref(endpoint, options: FindListOptions = {}): string {
|
||||||
let result;
|
let result;
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export abstract class ConfigService {
|
|||||||
distinctUntilChanged());
|
distinctUntilChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConfigBySearch(options: FindAllOptions = {}): Observable<ConfigData> {
|
public getConfigBySearch(options: FindListOptions = {}): Observable<ConfigData> {
|
||||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
map((endpoint: string) => this.getConfigSearchHref(endpoint, options)),
|
map((endpoint: string) => this.getConfigSearchHref(endpoint, options)),
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
|
@@ -128,8 +128,8 @@ import {
|
|||||||
MOCK_RESPONSE_MAP,
|
MOCK_RESPONSE_MAP,
|
||||||
MockResponseMap,
|
MockResponseMap,
|
||||||
mockResponseMap
|
mockResponseMap
|
||||||
} from './dspace-rest-v2/mocks/mock-response-map';
|
} from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map';
|
||||||
import { EndpointMockingRestService } from './dspace-rest-v2/endpoint-mocking-rest.service';
|
import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
|
||||||
import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config';
|
import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config';
|
||||||
import { SearchFilterService } from './shared/search/search-filter.service';
|
import { SearchFilterService } from './shared/search/search-filter.service';
|
||||||
import { SearchConfigurationService } from './shared/search/search-configuration.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 { RelationshipTypeService } from './data/relationship-type.service';
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.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) => {
|
export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => {
|
||||||
if (ENV_CONFIG.production) {
|
if (ENV_CONFIG.production) {
|
||||||
return new DSpaceRESTv2Service(http);
|
return new DSpaceRESTv2Service(http);
|
||||||
|
@@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
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 { Observable } from 'rxjs';
|
||||||
import { find, map, tap } from 'rxjs/operators';
|
import { find, map, tap } from 'rxjs/operators';
|
||||||
import { configureRequest, getResponseFromEntry } from '../shared/operators';
|
import { configureRequest, getResponseFromEntry } from '../shared/operators';
|
||||||
@@ -54,10 +54,10 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing bitstream formats
|
* Get the endpoint for browsing bitstream formats
|
||||||
* @param {FindAllOptions} options
|
* @param {FindListOptions} options
|
||||||
* @returns {Observable<string>}
|
* @returns {Observable<string>}
|
||||||
*/
|
*/
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { FindAllOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,10 +37,10 @@ export class BundleDataService extends DataService<Bundle> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing bundles
|
* Get the endpoint for browsing bundles
|
||||||
* @param {FindAllOptions} options
|
* @param {FindListOptions} options
|
||||||
* @returns {Observable<string>}
|
* @returns {Observable<string>}
|
||||||
*/
|
*/
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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 { Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
@@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { FindAllOptions, GetRequest } from './request.models';
|
import {FindListOptions, FindListRequest, GetRequest} from './request.models';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { configureRequest } from '../shared/operators';
|
import { configureRequest } from '../shared/operators';
|
||||||
@@ -50,11 +50,11 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
/**
|
/**
|
||||||
* Get all collections the user is authorized to submit to
|
* Get all collections the user is authorized to submit to
|
||||||
*
|
*
|
||||||
* @param options The [[FindAllOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||||
* collection list
|
* collection list
|
||||||
*/
|
*/
|
||||||
getAuthorizedCollection(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
getAuthorizedCollection(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
const searchHref = 'findAuthorized';
|
const searchHref = 'findAuthorized';
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
@@ -65,11 +65,11 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
* Get all collections the user is authorized to submit to, by community
|
* Get all collections the user is authorized to submit to, by community
|
||||||
*
|
*
|
||||||
* @param communityId The community id
|
* @param communityId The community id
|
||||||
* @param options The [[FindAllOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||||
* collection list
|
* collection list
|
||||||
*/
|
*/
|
||||||
getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
const searchHref = 'findAuthorizedByCommunity';
|
const searchHref = 'findAuthorizedByCommunity';
|
||||||
options = Object.assign({}, options, {
|
options = Object.assign({}, options, {
|
||||||
searchParams: [new SearchParam('uuid', communityId)]
|
searchParams: [new SearchParam('uuid', communityId)]
|
||||||
@@ -87,7 +87,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
*/
|
*/
|
||||||
hasAuthorizedCollection(): Observable<boolean> {
|
hasAuthorizedCollection(): Observable<boolean> {
|
||||||
const searchHref = 'findAuthorized';
|
const searchHref = 'findAuthorized';
|
||||||
const options = new FindAllOptions();
|
const options = new FindListOptions();
|
||||||
options.elementsPerPage = 1;
|
options.elementsPerPage = 1;
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
@@ -138,4 +138,10 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
return this.rdbService.buildList(href$);
|
return this.rdbService.buildList(href$);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getFindByParentHref(parentUUID: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint('communities').pipe(
|
||||||
|
switchMap((communityEndpointHref: string) =>
|
||||||
|
this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,12 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
|||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ComColDataService } from './comcol-data.service';
|
import { ComColDataService } from './comcol-data.service';
|
||||||
import { CommunityDataService } from './community-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 { RequestService } from './request.service';
|
||||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RequestEntry } from './request.reducer';
|
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 { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
@@ -45,6 +45,11 @@ class TestService extends ComColDataService<any> {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getFindByParentHref(parentUUID: string): Observable<string> {
|
||||||
|
// implementation in subclasses for communities/collections
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
@@ -66,7 +71,7 @@ describe('ComColDataService', () => {
|
|||||||
const dataBuildService = {} as NormalizedObjectBuildService;
|
const dataBuildService = {} as NormalizedObjectBuildService;
|
||||||
|
|
||||||
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
|
||||||
const options = Object.assign(new FindAllOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
scopeID: scopeID
|
scopeID: scopeID
|
||||||
});
|
});
|
||||||
const getRequestEntry$ = (successful: boolean) => {
|
const getRequestEntry$ = (successful: boolean) => {
|
||||||
|
@@ -1,12 +1,23 @@
|
|||||||
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 { 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 { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { CommunityDataService } from './community-data.service';
|
import { CommunityDataService } from './community-data.service';
|
||||||
|
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { FindAllOptions, FindByIDRequest } from './request.models';
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { FindListOptions, FindByIDRequest } from './request.models';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { getResponseFromEntry } from '../shared/operators';
|
import { getResponseFromEntry } from '../shared/operators';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
@@ -26,7 +37,7 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
|||||||
* @return { Observable<string> }
|
* @return { Observable<string> }
|
||||||
* an Observable<string> containing the scoped URL
|
* an Observable<string> containing the scoped URL
|
||||||
*/
|
*/
|
||||||
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
if (isEmpty(options.scopeID)) {
|
if (isEmpty(options.scopeID)) {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
} else {
|
} else {
|
||||||
@@ -57,4 +68,12 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
|||||||
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share());
|
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract getFindByParentHref(parentUUID: string): Observable<string>;
|
||||||
|
|
||||||
|
public findByParent(parentUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
|
const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options);
|
||||||
|
return this.findList(href$, options);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { filter, take } from 'rxjs/operators';
|
import { filter, switchMap, take } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
@@ -9,7 +9,7 @@ import { Community } from '../shared/community.model';
|
|||||||
import { ComColDataService } from './comcol-data.service';
|
import { ComColDataService } from './comcol-data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { FindAllOptions, FindAllRequest } from './request.models';
|
import { FindListOptions, FindListRequest } from './request.models';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -43,16 +43,24 @@ export class CommunityDataService extends ComColDataService<Community> {
|
|||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
|
findTop(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
|
||||||
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
|
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
|
||||||
hrefObs.pipe(
|
hrefObs.pipe(
|
||||||
filter((href: string) => hasValue(href)),
|
filter((href: string) => hasValue(href)),
|
||||||
take(1))
|
take(1))
|
||||||
.subscribe((href: string) => {
|
.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);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdbService.buildList<Community>(hrefObs) as Observable<RemoteData<PaginatedList<Community>>>;
|
return this.rdbService.buildList<Community>(hrefObs) as Observable<RemoteData<PaginatedList<Community>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getFindByParentHref(parentUUID: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
switchMap((communityEndpointHref: string) =>
|
||||||
|
this.halService.getEndpoint('subcommunities', `${communityEndpointHref}/${parentUUID}`))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
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 { SortDirection, SortOptions } from '../cache/models/sort-options.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { compare, Operation } from 'fast-json-patch';
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
@@ -42,7 +42,7 @@ class TestService extends DataService<any> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return observableOf(endpoint);
|
return observableOf(endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ class DummyChangeAnalyzer implements ChangeAnalyzer<NormalizedTestObject> {
|
|||||||
|
|
||||||
describe('DataService', () => {
|
describe('DataService', () => {
|
||||||
let service: TestService;
|
let service: TestService;
|
||||||
let options: FindAllOptions;
|
let options: FindListOptions;
|
||||||
const requestService = {generateRequestId: () => uuidv4()} as RequestService;
|
const requestService = {generateRequestId: () => uuidv4()} as RequestService;
|
||||||
const halService = {} as HALEndpointService;
|
const halService = {} as HALEndpointService;
|
||||||
const rdbService = {} as RemoteDataBuildService;
|
const rdbService = {} as RemoteDataBuildService;
|
||||||
@@ -192,7 +192,7 @@ describe('DataService', () => {
|
|||||||
dso2.self = selfLink;
|
dso2.self = selfLink;
|
||||||
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
||||||
|
|
||||||
spyOn(service, 'findByHref').and.returnValues(createSuccessfulRemoteDataObject$(dso));
|
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(dso));
|
||||||
spyOn(objectCache, 'addPatch');
|
spyOn(objectCache, 'addPatch');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -11,7 +11,14 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { CreateRequest, DeleteByIDRequest, FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
|
import {
|
||||||
|
CreateRequest,
|
||||||
|
DeleteByIDRequest,
|
||||||
|
FindListOptions,
|
||||||
|
FindListRequest,
|
||||||
|
FindByIDRequest,
|
||||||
|
GetRequest
|
||||||
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||||
@@ -47,17 +54,17 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
*/
|
*/
|
||||||
protected responseMsToLive: number;
|
protected responseMsToLive: number;
|
||||||
|
|
||||||
public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string>
|
public abstract getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the HREF with given options object
|
* 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
|
* @param linkPath The link path for the object
|
||||||
* @return {Observable<string>}
|
* @return {Observable<string>}
|
||||||
* Return an observable that emits created HREF
|
* Return an observable that emits created HREF
|
||||||
*/
|
*/
|
||||||
protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
|
protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||||
let result: Observable<string>;
|
let result: Observable<string>;
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
@@ -70,11 +77,11 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
* Create the HREF for a specific object's search method with given options object
|
* Create the HREF for a specific object's search method with given options object
|
||||||
*
|
*
|
||||||
* @param searchMethod The search method for the object
|
* @param searchMethod The search method for the object
|
||||||
* @param options The [[FindAllOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
* @return {Observable<string>}
|
* @return {Observable<string>}
|
||||||
* Return an observable that emits created HREF
|
* Return an observable that emits created HREF
|
||||||
*/
|
*/
|
||||||
protected getSearchByHref(searchMethod: string, options: FindAllOptions = {}): Observable<string> {
|
protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable<string> {
|
||||||
let result: Observable<string>;
|
let result: Observable<string>;
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
@@ -94,11 +101,11 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
*
|
*
|
||||||
* @param href$ The HREF to which the query string should be appended
|
* @param href$ The HREF to which the query string should be appended
|
||||||
* @param args Array with additional params to combine with query string
|
* @param args Array with additional params to combine with query string
|
||||||
* @param options The [[FindAllOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
* @return {Observable<string>}
|
* @return {Observable<string>}
|
||||||
* Return an observable that emits created HREF
|
* Return an observable that emits created HREF
|
||||||
*/
|
*/
|
||||||
protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindAllOptions): Observable<string> {
|
protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindListOptions): Observable<string> {
|
||||||
|
|
||||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
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 */
|
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||||
@@ -120,20 +127,22 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
findAll(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
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)))
|
first((href: string) => hasValue(href)))
|
||||||
.subscribe((href: string) => {
|
.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)) {
|
if (hasValue(this.responseMsToLive)) {
|
||||||
request.responseMsToLive = this.responseMsToLive;
|
request.responseMsToLive = this.responseMsToLive;
|
||||||
}
|
}
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdbService.buildList<T>(hrefObs) as Observable<RemoteData<PaginatedList<T>>>;
|
return this.rdbService.buildList<T>(href$) as Observable<RemoteData<PaginatedList<T>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,7 +157,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
findById(id: string): Observable<RemoteData<T>> {
|
findById(id: string): Observable<RemoteData<T>> {
|
||||||
|
|
||||||
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
|
map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
|
||||||
|
|
||||||
hrefObs.pipe(
|
hrefObs.pipe(
|
||||||
find((href: string) => hasValue(href)))
|
find((href: string) => hasValue(href)))
|
||||||
@@ -184,14 +193,14 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 searchMethod The search method for the object
|
||||||
* @param options The [[FindAllOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||||
* Return an observable that emits response from the server
|
* Return an observable that emits response from the server
|
||||||
*/
|
*/
|
||||||
protected searchBy(searchMethod: string, options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
protected searchBy(searchMethod: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
||||||
|
|
||||||
const hrefObs = this.getSearchByHref(searchMethod, options);
|
const hrefObs = this.getSearchByHref(searchMethod, options);
|
||||||
|
|
||||||
@@ -199,7 +208,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
find((href: string) => hasValue(href)),
|
find((href: string) => hasValue(href)),
|
||||||
tap((href: string) => {
|
tap((href: string) => {
|
||||||
this.requestService.removeByHrefSubstring(href);
|
this.requestService.removeByHrefSubstring(href);
|
||||||
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
|
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
|
||||||
request.responseMsToLive = 10 * 1000;
|
request.responseMsToLive = 10 * 1000;
|
||||||
|
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
|
@@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
|
|||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { FindAllOptions, FindByIDRequest, IdentifierType } from './request.models';
|
import { FindListOptions, FindByIDRequest, IdentifierType } from './request.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
@@ -40,7 +40,7 @@ export class DsoRedirectDataService extends DataService<any> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { FindAllOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
@@ -32,7 +32,7 @@ class DataServiceImpl extends DataService<DSpaceObject> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,14 +2,13 @@ import { Store } from '@ngrx/store';
|
|||||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { BrowseService } from '../browse/browse.service';
|
import { BrowseService } from '../browse/browse.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import {
|
import {
|
||||||
DeleteRequest,
|
DeleteRequest,
|
||||||
FindAllOptions,
|
FindListOptions,
|
||||||
GetRequest,
|
GetRequest,
|
||||||
MappedCollectionsRequest,
|
MappedCollectionsRequest,
|
||||||
PostRequest,
|
PostRequest,
|
||||||
@@ -58,7 +57,7 @@ describe('ItemDataService', () => {
|
|||||||
} as HALEndpointService;
|
} as HALEndpointService;
|
||||||
|
|
||||||
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
|
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
|
||||||
const options = Object.assign(new FindAllOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
scopeID: scopeID,
|
scopeID: scopeID,
|
||||||
sort: {
|
sort: {
|
||||||
field: '',
|
field: '',
|
||||||
|
@@ -14,7 +14,7 @@ import { RequestService } from './request.service';
|
|||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import {
|
import {
|
||||||
DeleteRequest,
|
DeleteRequest,
|
||||||
FindAllOptions,
|
FindListOptions,
|
||||||
MappedCollectionsRequest,
|
MappedCollectionsRequest,
|
||||||
PatchRequest,
|
PatchRequest,
|
||||||
PostRequest, PutRequest,
|
PostRequest, PutRequest,
|
||||||
@@ -59,10 +59,10 @@ export class ItemDataService extends DataService<Item> {
|
|||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing items
|
* Get the endpoint for browsing items
|
||||||
* (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
|
* (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<string>}
|
* @returns {Observable<string>}
|
||||||
*/
|
*/
|
||||||
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
let field = 'dc.date.issued';
|
let field = 'dc.date.issued';
|
||||||
if (options.sort && options.sort.field) {
|
if (options.sort && options.sort.field) {
|
||||||
field = options.sort.field;
|
field = options.sort.field;
|
||||||
@@ -247,4 +247,14 @@ export class ItemDataService extends DataService<Item> {
|
|||||||
map((request: RequestEntry) => request.response)
|
map((request: RequestEntry) => request.response)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint for an item's bitstreams
|
||||||
|
* @param itemId
|
||||||
|
*/
|
||||||
|
public getBitstreamsEndpoint(itemId: string): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.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 { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
@@ -33,7 +33,7 @@ class DataServiceImpl extends DataService<MetadataSchema> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { filter, find, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, find, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { configureRequest, getSucceededRemoteData } from '../shared/operators';
|
import { configureRequest, getSucceededRemoteData } from '../shared/operators';
|
||||||
import { FindAllOptions, FindAllRequest } from './request.models';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
@@ -12,6 +11,7 @@ import { PaginatedList } from './paginated-list';
|
|||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { ItemType } from '../shared/item-relationships/item-type.model';
|
import { ItemType } from '../shared/item-relationships/item-type.model';
|
||||||
import { isNotUndefined } from '../../shared/empty.util';
|
import { isNotUndefined } from '../../shared/empty.util';
|
||||||
|
import { FindListOptions, FindListRequest } from './request.models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all relationship requests
|
* The service handling all relationship requests
|
||||||
@@ -35,11 +35,11 @@ export class RelationshipTypeService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllRelationshipTypes(options: FindAllOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
getAllRelationshipTypes(options: FindListOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
||||||
const link$ = this.halService.getEndpoint(this.linkPath);
|
const link$ = this.halService.getEndpoint(this.linkPath);
|
||||||
return link$
|
return link$
|
||||||
.pipe(
|
.pipe(
|
||||||
map((endpointURL: string) => new FindAllRequest(this.requestService.generateRequestId(), endpointURL, options)),
|
map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
|
||||||
configureRequest(this.requestService),
|
configureRequest(this.requestService),
|
||||||
switchMap(() => this.rdbService.buildList(link$))
|
switchMap(() => this.rdbService.buildList(link$))
|
||||||
);
|
);
|
||||||
|
@@ -123,8 +123,8 @@ describe('RelationshipService', () => {
|
|||||||
it('should clear the related items their cache', () => {
|
it('should clear the related items their cache', () => {
|
||||||
expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self);
|
expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self);
|
||||||
expect(objectCache.remove).toHaveBeenCalledWith(item.self);
|
expect(objectCache.remove).toHaveBeenCalledWith(item.self);
|
||||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.self);
|
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.uuid);
|
||||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self);
|
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.uuid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -3,39 +3,20 @@ import { Injectable } from '@angular/core';
|
|||||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import {
|
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
distinctUntilChanged,
|
import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
filter,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
startWith,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
tap
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
import {
|
|
||||||
compareArraysUsingIds,
|
|
||||||
paginatedRelationsToItems,
|
|
||||||
relationsToItems
|
|
||||||
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
import {
|
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
hasValue,
|
|
||||||
hasValueOperator,
|
|
||||||
isNotEmpty,
|
|
||||||
isNotEmptyOperator
|
|
||||||
} from '../../shared/empty.util';
|
|
||||||
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
import {
|
import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
||||||
RemoveNameVariantAction,
|
|
||||||
SetNameVariantAction
|
|
||||||
} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
|
||||||
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
||||||
|
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
@@ -43,18 +24,11 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
import { Relationship } from '../shared/item-relationships/relationship.model';
|
import { Relationship } from '../shared/item-relationships/relationship.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import {
|
|
||||||
configureRequest,
|
|
||||||
getRemoteDataPayload,
|
|
||||||
getResponseFromEntry,
|
|
||||||
getSucceededRemoteData
|
|
||||||
} from '../shared/operators';
|
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { RemoteData, RemoteDataState } from './remote-data';
|
import { RemoteData, RemoteDataState } from './remote-data';
|
||||||
import { DeleteRequest, FindAllOptions, PostRequest, RestRequest } from './request.models';
|
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
|
|
||||||
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
||||||
@@ -89,7 +63,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +93,14 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<RestResponse> {
|
addRelationship(typeId: string, item1: Item, item2: Item, leftwardValue?: string, rightwardValue?: string): Observable<RestResponse> {
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
@@ -139,6 +121,10 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private removeRelationshipItemsFromCacheByRelationship(relationshipId: string) {
|
||||||
this.findById(relationshipId).pipe(
|
this.findById(relationshipId).pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
@@ -155,12 +141,16 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private removeRelationshipItemsFromCache(item) {
|
||||||
this.objectCache.remove(item.self);
|
this.objectCache.remove(item.self);
|
||||||
this.requestService.removeByHrefSubstring(item.uuid);
|
this.requestService.removeByHrefSubstring(item.uuid);
|
||||||
combineLatest(
|
combineLatest(
|
||||||
this.objectCache.hasBySelfLinkObservable(item.self),
|
this.objectCache.hasBySelfLinkObservable(item.self),
|
||||||
this.requestService.hasByHrefObservable(item.self)
|
this.requestService.hasByHrefObservable(item.uuid)
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
|
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
|
||||||
take(1),
|
take(1),
|
||||||
@@ -229,7 +219,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
* @param label
|
* @param label
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getRelatedItemsByLabel(item: Item, label: string, options?: FindAllOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
return this.getItemRelationshipsByLabel(item, label, options).pipe(paginatedRelationsToItems(item.uuid));
|
return this.getItemRelationshipsByLabel(item, label, options).pipe(paginatedRelationsToItems(item.uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,18 +230,18 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
* @param label
|
* @param label
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getItemRelationshipsByLabel(item: Item, label: string, options?: FindAllOptions): Observable<RemoteData<PaginatedList<Relationship>>> {
|
getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Relationship>>> {
|
||||||
let findAllOptions = new FindAllOptions();
|
let findListOptions = new FindListOptions();
|
||||||
if (options) {
|
if (options) {
|
||||||
findAllOptions = Object.assign(new FindAllOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
}
|
}
|
||||||
const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
|
const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
|
||||||
if (findAllOptions.searchParams) {
|
if (findListOptions.searchParams) {
|
||||||
findAllOptions.searchParams = [...findAllOptions.searchParams, ...searchParams];
|
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||||
} else {
|
} else {
|
||||||
findAllOptions.searchParams = searchParams;
|
findListOptions.searchParams = searchParams;
|
||||||
}
|
}
|
||||||
return this.searchBy('byLabel', findAllOptions);
|
return this.searchBy('byLabel', findListOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,6 +275,12 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Relationship> {
|
getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string): Observable<Relationship> {
|
||||||
return this.getItemRelationshipsByLabel(item1, label)
|
return this.getItemRelationshipsByLabel(item1, label)
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -314,24 +310,51 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public setNameVariant(listID: string, itemID: string, nameVariant: string) {
|
||||||
this.appStore.dispatch(new SetNameVariantAction(listID, itemID, nameVariant));
|
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<string> {
|
public getNameVariant(listID: string, itemID: string): Observable<string> {
|
||||||
return this.appStore.pipe(
|
return this.appStore.pipe(
|
||||||
select(relationshipStateSelector(listID, itemID))
|
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) {
|
public removeNameVariant(listID: string, itemID: string) {
|
||||||
this.appStore.dispatch(new RemoveNameVariantAction(listID, itemID));
|
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) {
|
public getNameVariantsByListID(listID: string) {
|
||||||
return this.appStore.pipe(select(relationshipListStateSelector(listID)));
|
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<RemoteData<Relationship>> {
|
public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable<RemoteData<Relationship>> {
|
||||||
const update$ = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
|
const update$ = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@@ -138,7 +138,7 @@ export class FindByIDRequest extends GetRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FindAllOptions {
|
export class FindListOptions {
|
||||||
scopeID?: string;
|
scopeID?: string;
|
||||||
elementsPerPage?: number;
|
elementsPerPage?: number;
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
@@ -147,11 +147,11 @@ export class FindAllOptions {
|
|||||||
startsWith?: string;
|
startsWith?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FindAllRequest extends GetRequest {
|
export class FindListRequest extends GetRequest {
|
||||||
constructor(
|
constructor(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
href: string,
|
href: string,
|
||||||
public body?: FindAllOptions,
|
public body?: FindListOptions,
|
||||||
) {
|
) {
|
||||||
super(uuid, href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { Observable } from 'rxjs';
|
|||||||
|
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
import { RequestService } from '../data/request.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 { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { ResourcePolicy } from '../shared/resource-policy.model';
|
import { ResourcePolicy } from '../shared/resource-policy.model';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
@@ -36,7 +36,7 @@ class DataServiceImpl extends DataService<ResourcePolicy> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
|
||||||
return this.halService.getEndpoint(linkPath);
|
return this.halService.getEndpoint(linkPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { RequestEntry } from './request.reducer';
|
import { RequestEntry } from './request.reducer';
|
||||||
import { FindAllOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
@@ -31,7 +31,7 @@ describe('SiteDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2';
|
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) => {
|
const getRequestEntry$ = (successful: boolean, statusCode: number, statusText: string) => {
|
||||||
return observableOf({
|
return observableOf({
|
||||||
|
@@ -10,7 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
import { FindAllOptions } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
@@ -42,10 +42,10 @@ export class SiteDataService extends DataService<Site> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing the site object
|
* Get the endpoint for browsing the site object
|
||||||
* @param {FindAllOptions} options
|
* @param {FindListOptions} options
|
||||||
* @param {Observable<string>} linkPath
|
* @param {Observable<string>} linkPath
|
||||||
*/
|
*/
|
||||||
getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string> {
|
getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { FindAllOptions } from '../data/request.models';
|
import { FindListOptions } from '../data/request.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ import { CacheableObject } from '../cache/object-cache.reducer';
|
|||||||
*/
|
*/
|
||||||
export abstract class EpersonService<TDomain extends CacheableObject> extends DataService<TDomain> {
|
export abstract class EpersonService<TDomain extends CacheableObject> extends DataService<TDomain> {
|
||||||
|
|
||||||
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
|
public getBrowseEndpoint(options: FindListOptions): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import { filter, map, take } from 'rxjs/operators';
|
|||||||
|
|
||||||
import { EpersonService } from './eperson.service';
|
import { EpersonService } from './eperson.service';
|
||||||
import { RequestService } from '../data/request.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 { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { Group } from './models/group.model';
|
import { Group } from './models/group.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
@@ -52,7 +52,7 @@ export class GroupEpersonService extends EpersonService<Group> {
|
|||||||
*/
|
*/
|
||||||
isMemberOf(groupName: string): Observable<boolean> {
|
isMemberOf(groupName: string): Observable<boolean> {
|
||||||
const searchHref = 'isMemberOf';
|
const searchHref = 'isMemberOf';
|
||||||
const options = new FindAllOptions();
|
const options = new FindListOptions();
|
||||||
options.searchParams = [new SearchParam('groupName', groupName)];
|
options.searchParams = [new SearchParam('groupName', groupName)];
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
|
@@ -195,6 +195,9 @@ export class RouteService {
|
|||||||
this.store.dispatch(new SetParameterAction(key, value));
|
this.store.dispatch(new SetParameterAction(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current route parameters and query parameters in the store
|
||||||
|
*/
|
||||||
public setCurrentRouteInfo() {
|
public setCurrentRouteInfo() {
|
||||||
combineLatest(this.getRouteParams(), this.route.queryParams)
|
combineLatest(this.getRouteParams(), this.route.queryParams)
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
|
@@ -43,8 +43,8 @@ export class HALEndpointService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEndpoint(linkPath: string): Observable<string> {
|
public getEndpoint(linkPath: string, startHref?: string): Observable<string> {
|
||||||
return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/'));
|
return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -125,9 +125,3 @@ export const getFirstOccurrence = () =>
|
|||||||
source.pipe(
|
source.pipe(
|
||||||
map((rd) => Object.assign(rd, { payload: rd.payload.page.length > 0 ? rd.payload.page[0] : undefined }))
|
map((rd) => Object.assign(rd, { payload: rd.payload.page.length > 0 ? rd.payload.page[0] : undefined }))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const obsLog = (logString?: string) =>
|
|
||||||
<T>(source: Observable<T>): Observable<T> =>
|
|
||||||
source.pipe(
|
|
||||||
tap((t) => console.log(logString || '', t))
|
|
||||||
);
|
|
||||||
|
@@ -8,7 +8,7 @@ import { DataService } from '../data/data.service';
|
|||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { WorkflowItem } from './models/workflowitem.model';
|
import { WorkflowItem } from './models/workflowitem.model';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.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 { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -35,7 +35,7 @@ export class WorkflowItemDataService extends DataService<WorkflowItem> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBrowseEndpoint(options: FindAllOptions) {
|
public getBrowseEndpoint(options: FindListOptions) {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.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 { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -35,7 +35,7 @@ export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBrowseEndpoint(options: FindAllOptions) {
|
public getBrowseEndpoint(options: FindListOptions) {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DataService } from '../data/data.service';
|
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 { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { ProcessTaskResponse } from './models/process-task-response';
|
import { ProcessTaskResponse } from './models/process-task-response';
|
||||||
@@ -18,7 +18,7 @@ import { CacheableObject } from '../cache/object-cache.reducer';
|
|||||||
*/
|
*/
|
||||||
export abstract class TasksService<T extends CacheableObject> extends DataService<T> {
|
export abstract class TasksService<T extends CacheableObject> extends DataService<T> {
|
||||||
|
|
||||||
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
|
public getBrowseEndpoint(options: FindListOptions): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,6 +5,10 @@ import { EquatableObject } from './equatable';
|
|||||||
const excludedFromEquals = new Map();
|
const excludedFromEquals = new Map();
|
||||||
const fieldsForEqualsMap = 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<EquatableObject<any>>) {
|
export function inheritEquatable(parentCo: GenericConstructor<EquatableObject<any>>) {
|
||||||
return function decorator(childCo: GenericConstructor<EquatableObject<any>>) {
|
return function decorator(childCo: GenericConstructor<EquatableObject<any>>) {
|
||||||
const parentExcludedFields = getExcludedFromEqualsFor(parentCo) || [];
|
const parentExcludedFields = getExcludedFromEqualsFor(parentCo) || [];
|
||||||
@@ -21,6 +25,11 @@ export function inheritEquatable(parentCo: GenericConstructor<EquatableObject<an
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to mark properties as excluded from the equals method
|
||||||
|
* @param object The object to exclude the property for
|
||||||
|
* @param propertyName The name of the property to exclude
|
||||||
|
*/
|
||||||
export function excludeFromEquals(object: any, propertyName: string): any {
|
export function excludeFromEquals(object: any, propertyName: string): any {
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return;
|
return;
|
||||||
@@ -37,6 +46,10 @@ export function getExcludedFromEqualsFor(constructor: Function): string[] {
|
|||||||
return excludedFromEquals.get(constructor) || [];
|
return excludedFromEquals.get(constructor) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to save the fields that are to be used for a certain property in the equals method for the given object
|
||||||
|
* @param fields The fields to use to equate the property of the object
|
||||||
|
*/
|
||||||
export function fieldsForEquals(...fields: string[]): any {
|
export function fieldsForEquals(...fields: string[]): any {
|
||||||
return function i(object: any, propertyName: string): any {
|
return function i(object: any, propertyName: string): any {
|
||||||
if (!object) {
|
if (!object) {
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
import { getExcludedFromEqualsFor, getFieldsForEquals } from './equals.decorators';
|
import { getExcludedFromEqualsFor, getFieldsForEquals } from './equals.decorators';
|
||||||
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to compare fields of two objects against each other
|
||||||
|
* @param object1 The first object for the comparison
|
||||||
|
* @param object2 The second object for the comparison
|
||||||
|
* @param fieldList The list of property/field names to compare
|
||||||
|
*/
|
||||||
function equalsByFields(object1, object2, fieldList): boolean {
|
function equalsByFields(object1, object2, fieldList): boolean {
|
||||||
const unequalProperty = fieldList.find((key) => {
|
const unequalProperty = fieldList.find((key) => {
|
||||||
if (object1[key] === object2[key]) {
|
if (object1[key] === object2[key]) {
|
||||||
@@ -27,6 +33,10 @@ function equalsByFields(object1, object2, fieldList): boolean {
|
|||||||
return hasNoValue(unequalProperty);
|
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<T> {
|
export abstract class EquatableObject<T> {
|
||||||
equals(other: T): boolean {
|
equals(other: T): boolean {
|
||||||
if (hasNoValue(other)) {
|
if (hasNoValue(other)) {
|
||||||
|
@@ -36,8 +36,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 w-100">
|
<div class="mt-5 w-100">
|
||||||
<ds-related-entities-search [item]="object"
|
<ds-tabbed-related-entities-search [item]="object"
|
||||||
[relationType]="'isJournalOfPublication'">
|
[relationTypes]="[{
|
||||||
</ds-related-entities-search>
|
label: 'isJournalOfPublication',
|
||||||
|
filter: 'isJournalOfPublication'
|
||||||
|
}]">
|
||||||
|
</ds-tabbed-related-entities-search>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -24,16 +24,6 @@
|
|||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<ds-related-items
|
|
||||||
[parentItem]="object"
|
|
||||||
[relationType]="'isPersonOfOrgUnit'"
|
|
||||||
[label]="'relationships.isPersonOf' | translate">
|
|
||||||
</ds-related-items>
|
|
||||||
<ds-related-items
|
|
||||||
[parentItem]="object"
|
|
||||||
[relationType]="'isProjectOfOrgUnit'"
|
|
||||||
[label]="'relationships.isProjectOf' | translate">
|
|
||||||
</ds-related-items>
|
|
||||||
<ds-related-items
|
<ds-related-items
|
||||||
[parentItem]="object"
|
[parentItem]="object"
|
||||||
[relationType]="'isPublicationOfOrgUnit'"
|
[relationType]="'isPublicationOfOrgUnit'"
|
||||||
@@ -49,4 +39,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-5 w-100">
|
||||||
|
<ds-tabbed-related-entities-search [item]="object"
|
||||||
|
[relationTypes]="[{
|
||||||
|
label: 'isOrgUnitOfPerson',
|
||||||
|
filter: 'isOrgUnitOfPerson',
|
||||||
|
configuration: 'person'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'isOrgUnitOfProject',
|
||||||
|
filter: 'isOrgUnitOfProject',
|
||||||
|
configuration: 'project'
|
||||||
|
}]">
|
||||||
|
</ds-tabbed-related-entities-search>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -53,8 +53,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 w-100">
|
<div class="mt-5 w-100">
|
||||||
<ds-related-entities-search [item]="object"
|
<ds-tabbed-related-entities-search [item]="object"
|
||||||
[relationType]="'isAuthorOfPublication'">
|
[relationTypes]="[{
|
||||||
</ds-related-entities-search>
|
label: 'isAuthorOfPublication',
|
||||||
|
filter: 'isAuthorOfPublication'
|
||||||
|
}]">
|
||||||
|
</ds-tabbed-related-entities-search>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -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<ItemSearchResult, Item> implements OnInit {
|
export class OrgUnitSearchResultListSubmissionElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> implements OnInit {
|
||||||
allSuggestions: string[];
|
allSuggestions: string[];
|
||||||
|
@@ -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<OrgUnitInputSuggestionsComponent>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -8,6 +8,6 @@
|
|||||||
{{'submission.sections.describe.relationship-lookup.name-variant.notification.content' | translate: { value: value } }}
|
{{'submission.sections.describe.relationship-lookup.name-variant.notification.content' | translate: { value: value } }}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer justify-content-between">
|
<div class="modal-footer justify-content-between">
|
||||||
<button type="button" class="btn btn-light" (click)="modal.close()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.confirm' | translate }}</button>
|
<button type="button" class="btn btn-light confirm-button" (click)="modal.close()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.confirm' | translate }}</button>
|
||||||
<button type="button" class="btn btn-light" (click)="modal.dismiss()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.decline' | translate }}</button>
|
<button type="button" class="btn btn-light decline-button" (click)="modal.dismiss()">{{'submission.sections.describe.relationship-lookup.name-variant.notification.decline' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,16 +3,23 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { NameVariantModalComponent } from './name-variant-modal.component';
|
import { NameVariantModalComponent } from './name-variant-modal.component';
|
||||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
describe('NameVariantModalComponent', () => {
|
describe('NameVariantModalComponent', () => {
|
||||||
let component: NameVariantModalComponent;
|
let component: NameVariantModalComponent;
|
||||||
let fixture: ComponentFixture<NameVariantModalComponent>;
|
let fixture: ComponentFixture<NameVariantModalComponent>;
|
||||||
|
let debugElement;
|
||||||
|
let modal;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
modal = jasmine.createSpyObj('modal', ['close', 'dismiss']);
|
||||||
|
}
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [NameVariantModalComponent],
|
declarations: [NameVariantModalComponent],
|
||||||
imports: [NgbModule.forRoot(), TranslateModule.forRoot()],
|
imports: [NgbModule.forRoot(), TranslateModule.forRoot()],
|
||||||
providers: [NgbActiveModal]
|
providers: [{ provide: NgbActiveModal, useValue: modal }]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -20,10 +27,27 @@ describe('NameVariantModalComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(NameVariantModalComponent);
|
fixture = TestBed.createComponent(NameVariantModalComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
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({
|
@Component({
|
||||||
selector: 'ds-name-variant-modal',
|
selector: 'ds-name-variant-modal',
|
||||||
templateUrl: './name-variant-modal.component.html',
|
templateUrl: './name-variant-modal.component.html',
|
||||||
|
@@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit {
|
|||||||
} as TextMenuItemModel,
|
} as TextMenuItemModel,
|
||||||
index: 0
|
index: 0
|
||||||
},
|
},
|
||||||
// {
|
/* Communities & Collections tree */
|
||||||
// id: 'browse_global_communities_and_collections',
|
{
|
||||||
// parentID: 'browse_global',
|
id: `browse_global_communities_and_collections`,
|
||||||
// active: false,
|
parentID: 'browse_global',
|
||||||
// visible: true,
|
active: false,
|
||||||
// model: {
|
visible: true,
|
||||||
// type: MenuItemType.LINK,
|
model: {
|
||||||
// text: 'menu.section.browse_global_communities_and_collections',
|
type: MenuItemType.LINK,
|
||||||
// link: '#'
|
text: `menu.section.browse_global_communities_and_collections`,
|
||||||
// } as LinkMenuItemModel,
|
link: `/community-list`
|
||||||
// },
|
} as LinkMenuItemModel
|
||||||
|
},
|
||||||
|
|
||||||
/* Statistics */
|
/* Statistics */
|
||||||
{
|
{
|
||||||
|
@@ -226,6 +226,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
super(componentFactoryResolver, layoutService, validationService);
|
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 {
|
ngOnInit(): void {
|
||||||
this.hasRelationLookup = hasValue(this.model.relationship);
|
this.hasRelationLookup = hasValue(this.model.relationship);
|
||||||
if (this.hasRelationLookup) {
|
if (this.hasRelationLookup) {
|
||||||
@@ -310,6 +313,9 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
return this.model.value.pipe(map((list: Array<SearchResult<DSpaceObject>>) => isNotEmpty(list)));
|
return this.model.value.pipe(map((list: Array<SearchResult<DSpaceObject>>) => isNotEmpty(list)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a modal where the user can select relationships to be added to item being submitted
|
||||||
|
*/
|
||||||
openLookup() {
|
openLookup() {
|
||||||
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
||||||
size: 'lg'
|
size: 'lg'
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ExistingMetadataListElementComponent } from './existing-metadata-list-element.component';
|
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', () => {
|
describe('ExistingMetadataListElementComponent', () => {
|
||||||
let component: ExistingMetadataListElementComponent;
|
let component: ExistingMetadataListElementComponent;
|
||||||
@@ -8,9 +11,14 @@ describe('ExistingMetadataListElementComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ ExistingMetadataListElementComponent ]
|
declarations: [ExistingMetadataListElementComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SelectableListService, useValue: {} },
|
||||||
|
{ provide: Store, useValue: {} },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -25,6 +25,7 @@ import { RelationshipOptions } from '../../models/relationship-options.model';
|
|||||||
import { DynamicConcatModel } from '../models/ds-dynamic-concat.model';
|
import { DynamicConcatModel } from '../models/ds-dynamic-concat.model';
|
||||||
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
||||||
|
|
||||||
|
// tslint:disable:max-classes-per-file
|
||||||
export abstract class Reorderable {
|
export abstract class Reorderable {
|
||||||
|
|
||||||
constructor(public oldIndex?: number, public newIndex?: number) {
|
constructor(public oldIndex?: number, public newIndex?: number) {
|
||||||
@@ -174,3 +175,4 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// tslint:enable:max-classes-per-file
|
||||||
|
@@ -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<DsDynamicDisabledComponent>;
|
||||||
|
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('');
|
||||||
|
});
|
||||||
|
});
|
@@ -3,8 +3,10 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|||||||
import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { DynamicDisabledModel } from './dynamic-disabled.model';
|
import { DynamicDisabledModel } from './dynamic-disabled.model';
|
||||||
import { RelationshipTypeService } from '../../../../../../core/data/relationship-type.service';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing a simple disabled input field
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-disabled',
|
selector: 'ds-dynamic-disabled',
|
||||||
templateUrl: './dynamic-disabled.component.html'
|
templateUrl: './dynamic-disabled.component.html'
|
||||||
@@ -21,8 +23,7 @@ export class DsDynamicDisabledComponent extends DynamicFormControlComponent {
|
|||||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
constructor(protected layoutService: DynamicFormLayoutService,
|
constructor(protected layoutService: DynamicFormLayoutService,
|
||||||
protected validationService: DynamicFormValidationService,
|
protected validationService: DynamicFormValidationService
|
||||||
protected relationshipTypeService: RelationshipTypeService
|
|
||||||
) {
|
) {
|
||||||
super(layoutService, validationService);
|
super(layoutService, validationService);
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,9 @@ export interface DsDynamicDisabledModelConfig extends DsDynamicInputModelConfig
|
|||||||
value?: any;
|
value?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model represents the data for a disabled input field
|
||||||
|
*/
|
||||||
export class DynamicDisabledModel extends DsDynamicInputModel {
|
export class DynamicDisabledModel extends DsDynamicInputModel {
|
||||||
|
|
||||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DISABLED;
|
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DISABLED;
|
||||||
@@ -14,7 +17,6 @@ export class DynamicDisabledModel extends DsDynamicInputModel {
|
|||||||
|
|
||||||
constructor(config: DsDynamicDisabledModelConfig, layout?: DynamicFormControlLayout) {
|
constructor(config: DsDynamicDisabledModelConfig, layout?: DynamicFormControlLayout) {
|
||||||
super(config, layout);
|
super(config, layout);
|
||||||
|
|
||||||
this.readOnly = true;
|
this.readOnly = true;
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
this.valueUpdates.next(config.value);
|
this.valueUpdates.next(config.value);
|
||||||
|
@@ -30,6 +30,9 @@ import { Context } from '../../../../../core/shared/context.model';
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy {
|
||||||
label: string;
|
label: string;
|
||||||
relationshipOptions: RelationshipOptions;
|
relationshipOptions: RelationshipOptions;
|
||||||
|
@@ -11,7 +11,9 @@ export const NameVariantActionTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/**
|
||||||
|
* Abstract class for actions that happen to name variants
|
||||||
|
*/
|
||||||
export abstract class NameVariantListAction implements Action {
|
export abstract class NameVariantListAction implements Action {
|
||||||
type;
|
type;
|
||||||
payload: {
|
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 {
|
export class SetNameVariantAction extends NameVariantListAction {
|
||||||
type = NameVariantActionTypes.SET_NAME_VARIANT;
|
type = NameVariantActionTypes.SET_NAME_VARIANT;
|
||||||
payload: {
|
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 {
|
export class RemoveNameVariantAction extends NameVariantListAction {
|
||||||
type = NameVariantActionTypes.REMOVE_NAME_VARIANT;
|
type = NameVariantActionTypes.REMOVE_NAME_VARIANT;
|
||||||
constructor(listID: string, itemID: string) {
|
constructor(listID: string, itemID: string) {
|
||||||
|
@@ -73,6 +73,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$
|
@Effect({ dispatch: false }) updateNameVariantsActions$ = this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
ofType(RelationshipActionTypes.UPDATE_RELATIONSHIP),
|
ofType(RelationshipActionTypes.UPDATE_RELATIONSHIP),
|
||||||
|
@@ -24,9 +24,11 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
let item1;
|
let item1;
|
||||||
let item2;
|
let item2;
|
||||||
let item3;
|
let item3;
|
||||||
|
let item4;
|
||||||
let searchResult1;
|
let searchResult1;
|
||||||
let searchResult2;
|
let searchResult2;
|
||||||
let searchResult3;
|
let searchResult3;
|
||||||
|
let searchResult4;
|
||||||
let listID;
|
let listID;
|
||||||
let selection$;
|
let selection$;
|
||||||
|
|
||||||
@@ -39,9 +41,11 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
||||||
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
||||||
item3 = Object.assign(new Item(), { uuid: 'c3bcbff5-ec0c-4831-8e4c-94b9c933ccac' });
|
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 });
|
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
||||||
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
||||||
searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 });
|
searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 });
|
||||||
|
searchResult4 = Object.assign(new ItemSearchResult(), { indexableObject: item4 });
|
||||||
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
||||||
selection$ = observableOf([searchResult1, searchResult2]);
|
selection$ = observableOf([searchResult1, searchResult2]);
|
||||||
|
|
||||||
@@ -93,12 +97,12 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
describe('selectPage', () => {
|
describe('selectPage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(component.selectObject, 'emit');
|
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', () => {
|
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(component.selectObject.emit).toHaveBeenCalledWith(searchResult4);
|
||||||
expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
|
expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult4]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -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 {
|
export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
|
||||||
@Input() relationship: RelationshipOptions;
|
@Input() relationship: RelationshipOptions;
|
||||||
@Input() listId: string;
|
@Input() listId: string;
|
||||||
@@ -63,6 +66,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the pagination and fixed query parameters
|
||||||
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.resetRoute();
|
this.resetRoute();
|
||||||
this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
|
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() {
|
resetRoute() {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a page in the store
|
||||||
|
* @param page The page to select
|
||||||
|
*/
|
||||||
selectPage(page: Array<SearchResult<Item>>) {
|
selectPage(page: Array<SearchResult<Item>>) {
|
||||||
this.selection$
|
this.selection$
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
@@ -106,6 +119,10 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
this.selectableListService.select(this.listId, page);
|
this.selectableListService.select(this.listId, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselects a page in the store
|
||||||
|
* @param page the page to deselect
|
||||||
|
*/
|
||||||
deselectPage(page: Array<SearchResult<Item>>) {
|
deselectPage(page: Array<SearchResult<Item>>) {
|
||||||
this.allSelected = false;
|
this.allSelected = false;
|
||||||
this.selection$
|
this.selection$
|
||||||
@@ -117,6 +134,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
this.selectableListService.deselect(this.listId, page);
|
this.selectableListService.deselect(this.listId, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select all items that were found using the current search query
|
||||||
|
*/
|
||||||
selectAll() {
|
selectAll() {
|
||||||
this.allSelected = true;
|
this.allSelected = true;
|
||||||
this.selectAllLoading = true;
|
this.selectAllLoading = true;
|
||||||
@@ -142,6 +162,9 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect all items
|
||||||
|
*/
|
||||||
deselectAll() {
|
deselectAll() {
|
||||||
this.allSelected = false;
|
this.allSelected = false;
|
||||||
this.selection$
|
this.selection$
|
||||||
|
@@ -1,46 +1,59 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { VarDirective } from '../../../../../utils/var.directive';
|
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 { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
||||||
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model';
|
||||||
import { Item } from '../../../../../../core/shared/item.model';
|
import { Item } from '../../../../../../core/shared/item.model';
|
||||||
import { DsDynamicLookupRelationSelectionTabComponent } from './dynamic-lookup-relation-selection-tab.component';
|
import { DsDynamicLookupRelationSelectionTabComponent } from './dynamic-lookup-relation-selection-tab.component';
|
||||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
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', () => {
|
describe('DsDynamicLookupRelationSelectionTabComponent', () => {
|
||||||
let component: DsDynamicLookupRelationSelectionTabComponent;
|
let component: DsDynamicLookupRelationSelectionTabComponent;
|
||||||
let fixture: ComponentFixture<DsDynamicLookupRelationSelectionTabComponent>;
|
let fixture: ComponentFixture<DsDynamicLookupRelationSelectionTabComponent>;
|
||||||
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 item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
||||||
let item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
let item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
||||||
let searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
let searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
||||||
let searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
let searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
||||||
let listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
let listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
||||||
let selection$ = observableOf([searchResult1, searchResult2]);
|
let selection$;
|
||||||
|
let selectionRD$;
|
||||||
|
let router;
|
||||||
|
|
||||||
function init() {
|
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' });
|
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
||||||
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
||||||
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
||||||
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
||||||
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
||||||
selection$ = observableOf([searchResult1, searchResult2]);
|
selection$ = observableOf([searchResult1, searchResult2]);
|
||||||
|
selectionRD$ = createSelection([searchResult1, searchResult2]);
|
||||||
|
router = jasmine.createSpyObj('router', ['navigate'])
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [DsDynamicLookupRelationSelectionTabComponent, VarDirective],
|
declarations: [DsDynamicLookupRelationSelectionTabComponent, VarDirective],
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
imports: [TranslateModule.forRoot()],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: SearchConfigurationService, useValue: {
|
provide: SearchConfigurationService, useValue: {
|
||||||
paginatedSearchOptions: observableOf(pSearchOptions)
|
paginatedSearchOptions: observableOf(pSearchOptions)
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -59,4 +72,26 @@ describe('DsDynamicLookupRelationSelectionTabComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
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<RemoteData<PaginatedList<ListableObject>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(undefined, content));
|
||||||
|
}
|
||||||
|
@@ -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 {
|
export class DsDynamicLookupRelationSelectionTabComponent {
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
@Input() listId: string;
|
@Input() listId: string;
|
||||||
@@ -44,6 +47,9 @@ export class DsDynamicLookupRelationSelectionTabComponent {
|
|||||||
private searchConfigService: SearchConfigurationService) {
|
private searchConfigService: SearchConfigurationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the selection and pagination on load
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.resetRoute();
|
this.resetRoute();
|
||||||
this.selectionRD$ = this.searchConfigService.paginatedSearchOptions
|
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() {
|
resetRoute() {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user