From de4b32dcad7985a81f28298a1af02b80603ae794 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 29 Nov 2019 17:51:27 +0100 Subject: [PATCH] 67478: Intermediate commit - Basic components --- .../normalized-external-source-entry.model.ts | 33 ++++++++ .../normalized-external-source.model.ts | 26 +++++++ src/app/core/core.module.ts | 8 +- src/app/core/data/external-source.service.ts | 77 +++++++++++++++++++ .../shared/external-source-entry.model.ts | 43 +++++++++++ src/app/core/shared/external-source.model.ts | 29 +++++++ src/app/core/shared/hal-endpoint.service.ts | 4 +- .../research-entities.module.ts | 4 +- ...try-list-submission-element.component.html | 1 + ...try-list-submission-element.component.scss | 0 ...entry-list-submission-element.component.ts | 16 ++++ ...elation-external-source-tab.component.html | 23 ++++++ ...elation-external-source-tab.component.scss | 0 ...-relation-external-source-tab.component.ts | 63 +++++++++++++++ src/app/shared/shared.module.ts | 4 +- 15 files changed, 326 insertions(+), 5 deletions(-) create mode 100644 src/app/core/cache/models/normalized-external-source-entry.model.ts create mode 100644 src/app/core/cache/models/normalized-external-source.model.ts create mode 100644 src/app/core/data/external-source.service.ts create mode 100644 src/app/core/shared/external-source-entry.model.ts create mode 100644 src/app/core/shared/external-source.model.ts create mode 100644 src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.html create mode 100644 src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.scss create mode 100644 src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts create mode 100644 src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.html create mode 100644 src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.scss create mode 100644 src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts diff --git a/src/app/core/cache/models/normalized-external-source-entry.model.ts b/src/app/core/cache/models/normalized-external-source-entry.model.ts new file mode 100644 index 0000000000..02abbb32d8 --- /dev/null +++ b/src/app/core/cache/models/normalized-external-source-entry.model.ts @@ -0,0 +1,33 @@ +import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; +import { NormalizedObject } from './normalized-object.model'; +import { ExternalSourceEntry } from '../../shared/external-source-entry.model'; +import { mapsTo } from '../builders/build-decorators'; +import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models'; + +@mapsTo(ExternalSourceEntry) +@inheritSerialization(NormalizedObject) +export class NormalizedExternalSourceEntry extends NormalizedObject { + /** + * Unique identifier + */ + @autoserialize + id: string; + + /** + * The value to display + */ + @autoserialize + display: string; + + /** + * The value to store the entry with + */ + @autoserialize + value: string; + + /** + * Metadata of the entry + */ + @autoserializeAs(MetadataMapSerializer) + metadata: MetadataMap; +} diff --git a/src/app/core/cache/models/normalized-external-source.model.ts b/src/app/core/cache/models/normalized-external-source.model.ts new file mode 100644 index 0000000000..dbcaeaf0de --- /dev/null +++ b/src/app/core/cache/models/normalized-external-source.model.ts @@ -0,0 +1,26 @@ +import { autoserialize, inheritSerialization } from 'cerialize'; +import { NormalizedObject } from './normalized-object.model'; +import { ExternalSource } from '../../shared/external-source.model'; +import { mapsTo } from '../builders/build-decorators'; + +@mapsTo(ExternalSource) +@inheritSerialization(NormalizedObject) +export class NormalizedExternalSource extends NormalizedObject { + /** + * Unique identifier + */ + @autoserialize + id: string; + + /** + * The name of this external source + */ + @autoserialize + name: string; + + /** + * Is the source hierarchical? + */ + @autoserialize + hierarchical: boolean; +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f14c712808..e94dc235a7 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -134,6 +134,9 @@ import { SearchConfigurationService } from './shared/search/search-configuration import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service'; import { RelationshipTypeService } from './data/relationship-type.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; +import { NormalizedExternalSource } from './cache/models/normalized-external-source.model'; +import { NormalizedExternalSourceEntry } from './cache/models/normalized-external-source-entry.model'; +import { ExternalSourceService } from './data/external-source.service'; export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => { if (ENV_CONFIG.production) { @@ -240,6 +243,7 @@ const PROVIDERS = [ SearchConfigurationService, SelectableListService, RelationshipTypeService, + ExternalSourceService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, @@ -284,7 +288,9 @@ export const normalizedModels = NormalizedPoolTask, NormalizedRelationship, NormalizedRelationshipType, - NormalizedItemType + NormalizedItemType, + NormalizedExternalSource, + NormalizedExternalSourceEntry ]; @NgModule({ diff --git a/src/app/core/data/external-source.service.ts b/src/app/core/data/external-source.service.ts new file mode 100644 index 0000000000..a408448f05 --- /dev/null +++ b/src/app/core/data/external-source.service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@angular/core'; +import { DataService } from './data.service'; +import { ExternalSource } from '../shared/external-source.model'; +import { RequestService } from './request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { FindAllOptions, GetRequest } from './request.models'; +import { Observable } from 'rxjs/internal/Observable'; +import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; +import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; +import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; +import { configureRequest } from '../shared/operators'; +import { RemoteData } from './remote-data'; +import { PaginatedList } from './paginated-list'; +import { ExternalSourceEntry } from '../shared/external-source-entry.model'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; + +@Injectable() +export class ExternalSourceService extends DataService { + protected linkPath = 'externalsources'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected dataBuildService: NormalizedObjectBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } + + getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + return this.halService.getEndpoint(linkPath); + } + + /** + * Get the endpoint for an external source's entries + * @param externalSourceId The id of the external source to fetch entries for + */ + getEntriesEndpoint(externalSourceId: string): Observable { + return this.getBrowseEndpoint().pipe( + map((href) => this.getIDHref(href, externalSourceId)), + switchMap((href) => this.halService.getEndpoint('entries', href)) + ); + } + + /** + * Get the entries for an external source + * @param externalSourceId The id of the external source to fetch entries for + * @param searchOptions The search options to limit results to + */ + getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions): Observable>> { + const requestUuid = this.requestService.generateRequestId(); + + const href$ = this.getEntriesEndpoint(externalSourceId).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint) + ); + + href$.pipe( + map((endpoint: string) => new GetRequest(requestUuid, endpoint)), + configureRequest(this.requestService) + ).subscribe(); + + return this.rdbService.buildList(href$); + } +} diff --git a/src/app/core/shared/external-source-entry.model.ts b/src/app/core/shared/external-source-entry.model.ts new file mode 100644 index 0000000000..be52f96b07 --- /dev/null +++ b/src/app/core/shared/external-source-entry.model.ts @@ -0,0 +1,43 @@ +import { MetadataMap } from './metadata.models'; +import { ResourceType } from './resource-type'; +import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; +import { GenericConstructor } from './generic-constructor'; + +/** + * Model class for a single entry from an external source + */ +export class ExternalSourceEntry extends ListableObject { + static type = new ResourceType('externalSourceEntry'); + + /** + * Unique identifier + */ + id: string; + + /** + * The value to display + */ + display: string; + + /** + * The value to store the entry with + */ + value: string; + + /** + * Metadata of the entry + */ + metadata: MetadataMap; + + /** + * The link to the rest endpoint where this External Source Entry can be found + */ + self: string; + + /** + * Method that returns as which type of object this object should be rendered + */ + getRenderTypes(): Array> { + return [this.constructor as GenericConstructor]; + } +} diff --git a/src/app/core/shared/external-source.model.ts b/src/app/core/shared/external-source.model.ts new file mode 100644 index 0000000000..a158f18f5d --- /dev/null +++ b/src/app/core/shared/external-source.model.ts @@ -0,0 +1,29 @@ +import { ResourceType } from './resource-type'; +import { CacheableObject } from '../cache/object-cache.reducer'; + +/** + * Model class for an external source + */ +export class ExternalSource extends CacheableObject { + static type = new ResourceType('externalsource'); + + /** + * Unique identifier + */ + id: string; + + /** + * The name of this external source + */ + name: string; + + /** + * Is the source hierarchical? + */ + hierarchical: boolean; + + /** + * The link to the rest endpoint where this External Source can be found + */ + self: string; +} diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index a93d54db64..117cc074ca 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -43,8 +43,8 @@ export class HALEndpointService { ); } - public getEndpoint(linkPath: string): Observable { - return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/')); + public getEndpoint(linkPath: string, startHref?: string): Observable { + return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/')); } /** diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts index 86c2a375da..cef3b4539b 100644 --- a/src/app/entity-groups/research-entities/research-entities.module.ts +++ b/src/app/entity-groups/research-entities/research-entities.module.ts @@ -25,6 +25,7 @@ import { PersonInputSuggestionsComponent } from './submission/item-list-elements import { NameVariantModalComponent } from './submission/name-variant-modal/name-variant-modal.component'; import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component'; import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component'; +import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component'; const ENTRY_COMPONENTS = [ OrgUnitComponent, @@ -48,7 +49,8 @@ const ENTRY_COMPONENTS = [ PersonInputSuggestionsComponent, NameVariantModalComponent, OrgUnitSearchResultListSubmissionElementComponent, - OrgUnitInputSuggestionsComponent + OrgUnitInputSuggestionsComponent, + ExternalSourceEntryListSubmissionElementComponent ]; @NgModule({ diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.html new file mode 100644 index 0000000000..785d0dd121 --- /dev/null +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.html @@ -0,0 +1 @@ +
Listable external source works! Display: {{object.value}}
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.scss b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts new file mode 100644 index 0000000000..7a6b051451 --- /dev/null +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts @@ -0,0 +1,16 @@ +import { AbstractListableElementComponent } from '../../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { ExternalSourceEntry } from '../../../../../core/shared/external-source-entry.model'; +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { Component } from '@angular/core'; + +@listableObjectComponent(ExternalSourceEntry, ViewMode.ListElement, Context.SubmissionModal) +@Component({ + selector: 'ds-external-source-entry-list-submission-element', + styleUrls: ['./external-source-entry-list-submission-element.component.scss'], + templateUrl: './external-source-entry-list-submission-element.component.html' +}) +export class ExternalSourceEntryListSubmissionElementComponent extends AbstractListableElementComponent { + +} diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.html new file mode 100644 index 0000000000..5105548306 --- /dev/null +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.html @@ -0,0 +1,23 @@ +
+
+

{{ 'submission.sections.describe.relationship-lookup.selection-tab.settings' | translate}}

+ +
+
+
+ {{'submission.sections.describe.relationship-lookup.selection-tab.no-selection' | translate}} +
+
+

{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + label | translate}}

+ +
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts new file mode 100644 index 0000000000..c138e4c1ae --- /dev/null +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts @@ -0,0 +1,63 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; +import { Router } from '@angular/router'; +import { ExternalSourceService } from '../../../../../../core/data/external-source.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { RemoteData } from '../../../../../../core/data/remote-data'; +import { PaginatedList } from '../../../../../../core/data/paginated-list'; +import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model'; +import { ExternalSource } from '../../../../../../core/shared/external-source.model'; +import { switchMap } from 'rxjs/operators'; +import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model'; +import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; +import { Context } from '../../../../../../core/shared/context.model'; +import { ListableObject } from '../../../../../object-collection/shared/listable-object.model'; + +@Component({ + selector: 'ds-dynamic-lookup-relation-external-source-tab', + styleUrls: ['./dynamic-lookup-relation-external-source-tab.component.scss'], + templateUrl: './dynamic-lookup-relation-external-source-tab.component.html', + providers: [ + { + provide: SEARCH_CONFIG_SERVICE, + useClass: SearchConfigurationService + } + ] +}) + +export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit { + @Input() label: string; + @Input() listId: string; + @Input() repeatable: boolean; + @Input() context: Context; + @Output() deselectObject: EventEmitter = new EventEmitter(); + @Output() selectObject: EventEmitter = new EventEmitter(); + + initialPagination = Object.assign(new PaginationComponentOptions(), { + id: 'submission-external-source-relation-list', + pageSize: 5 + }); + + /** + * The external source we're selecting entries for + */ + @Input() externalSource: ExternalSource; + + /** + * The displayed list of entries + */ + entriesRD$: Observable>>; + + constructor(private router: Router, + private searchConfigService: SearchConfigurationService, + private externalSourceService: ExternalSourceService) { + } + + ngOnInit(): void { + this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe( + switchMap((searchOptions: PaginatedSearchOptions) => + this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions)) + ) + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 85d001286d..903083e931 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -174,6 +174,7 @@ import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.componen import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component'; import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component'; import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; +import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -391,7 +392,8 @@ const ENTRY_COMPONENTS = [ SearchFacetRangeOptionComponent, SearchAuthorityFilterComponent, DsDynamicLookupRelationSearchTabComponent, - DsDynamicLookupRelationSelectionTabComponent + DsDynamicLookupRelationSelectionTabComponent, + DsDynamicLookupRelationExternalSourceTabComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [