diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts index 5a905fc7ea..cff1310f22 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts @@ -1,9 +1,13 @@ import { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { MetadataSchemaDataService } from '../../../../core/data/metadata-schema-data.service'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; +import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import { hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { RegistryService } from '../../../../core/registry/registry.service'; import { cloneDeep } from 'lodash'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { map, take } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; @@ -11,6 +15,7 @@ import { NgModel } from '@angular/forms'; import { MetadatumViewModel } from '../../../../core/shared/metadata.models'; import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; +import { followLink } from '../../../../shared/utils/follow-link-config.model'; @Component({ // tslint:disable-next-line:component-selector @@ -60,6 +65,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { constructor( private registryService: RegistryService, private objectUpdatesService: ObjectUpdatesService, + private metadataSchemaService: MetadataSchemaDataService ) { } @@ -128,25 +134,53 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { */ findMetadataFieldSuggestions(query: string): void { if (isNotEmpty(query)) { - this.registryService.queryMetadataFields(query).pipe( - // getSucceededRemoteData(), + this.registryService.queryMetadataFields(query, null, followLink('schema')).pipe( + getSucceededRemoteData(), take(1), - map((data) => data.payload.page) - ).subscribe( - (fields: MetadataField[]) => this.metadataFieldSuggestions.next( - fields.map((field: MetadataField) => { - return { - displayValue: field.toString().split('.').join('.​'), - value: field.toString() - }; - }) - ) - ); + map((data) => data.payload.page), + switchMap((fields: MetadataField[]) => { + return fields.map((field: MetadataField, index: number) => { + // Resolve the metadata field's schema if not already the case, to be able to form complete MD field name + if (!hasValue(field.schemaResolved)) { + field.schema.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + map((schema: MetadataSchema) => { + field.schemaResolved = schema; + if (index == fields.length - 1) { + this.setInputSuggestions(fields); + } + }), + take(1) + ).subscribe() + } else { + this.setInputSuggestions(fields); + } + }); + } + ), + take(1)).subscribe(); } else { this.metadataFieldSuggestions.next([]); } } + /** + * Set the list of input suggestion with the given Metadata fields, which all require a resolved MetadataSchema + * @param fields list of Metadata fields, which all require a resolved MetadataSchema + */ + setInputSuggestions(fields: MetadataField[]) { + this.metadataFieldSuggestions.next( + fields.map((field: MetadataField) => { + const fieldNameWhole = field.schemaResolved.prefix + '.' + field.toString(); + return { + displayValue: fieldNameWhole.split('.').join('.​'), + value: fieldNameWhole + }; + }) + ); + } + /** * Check if a user should be allowed to edit this field * @return an observable that emits true when the user should be able to edit this field and false when they should not diff --git a/src/app/core/data/metadata-field-data.service.ts b/src/app/core/data/metadata-field-data.service.ts index 1f7a4b9089..dddde2b93e 100644 --- a/src/app/core/data/metadata-field-data.service.ts +++ b/src/app/core/data/metadata-field-data.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { hasValue } from '../../shared/empty.util'; import { dataService } from '../cache/builders/build-decorators'; import { DataService } from './data.service'; import { RequestService } from './request.service'; @@ -27,6 +28,7 @@ import { RequestParam } from '../cache/models/request-param.model'; export class MetadataFieldDataService extends DataService { protected linkPath = 'metadatafields'; protected searchBySchemaLinkPath = 'bySchema'; + protected searchByFieldNameLinkPath = 'byFieldName'; constructor( protected requestService: RequestService, @@ -53,6 +55,29 @@ export class MetadataFieldDataService extends DataService { return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, ...linksToFollow); } + /** + * Find metadata fields with either the partial metadata field name (e.g. "dc.ti") as query or an exact match to + * at least the schema, element or qualifier + * @param schema optional; an exact match of the prefix of the metadata schema (e.g. "dc", "dcterms", "eperson") + * @param element optional; an exact match of the field's element (e.g. "contributor", "title") + * @param qualifier optional; an exact match of the field's qualifier (e.g. "author", "alternative") + * @param query optional (if any of schema, element or qualifier used) - part of the fully qualified field, + * should start with the start of the schema (e.g. "dc.ti") + * @param options The options info used to retrieve the fields + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved + */ + findByFieldName(schema: string, element: string, qualifier: string, query: string, options: FindListOptions = {}, ...linksToFollow: Array>) { + const optionParams = Object.assign(new FindListOptions(), options, { + searchParams: [ + new RequestParam('schema', hasValue(schema) ? schema : ''), + new RequestParam('element', hasValue(element) ? element : ''), + new RequestParam('qualifier', hasValue(qualifier) ? qualifier : ''), + new RequestParam('query', hasValue(query) ? query : '') + ] + }); + return this.searchBy(this.searchByFieldNameLinkPath, optionParams, ...linksToFollow); + } + /** * Clear all metadata field requests * Used for refreshing lists after adding/updating/removing a metadata field from a metadata schema diff --git a/src/app/core/metadata/metadata-field.model.ts b/src/app/core/metadata/metadata-field.model.ts index 66171141c5..41dfa784a4 100644 --- a/src/app/core/metadata/metadata-field.model.ts +++ b/src/app/core/metadata/metadata-field.model.ts @@ -1,10 +1,13 @@ import { autoserialize, deserialize } from 'cerialize'; -import { isNotEmpty } from '../../shared/empty.util'; +import { map } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { link, typedObject } from '../cache/builders/build-decorators'; +import { MetadataSchemaDataService } from '../data/metadata-schema-data.service'; import { GenericConstructor } from '../shared/generic-constructor'; import { HALLink } from '../shared/hal-link.model'; import { HALResource } from '../shared/hal-resource.model'; +import { getRemoteDataPayload, getSucceededRemoteData } from '../shared/operators'; import { ResourceType } from '../shared/resource-type'; import { excludeFromEquals } from '../utilities/equals.decorators'; import { METADATA_FIELD } from './metadata-field.resource-type'; @@ -67,9 +70,11 @@ export class MetadataField extends ListableObject implements HALResource { @link(METADATA_SCHEMA) schema?: Observable>; + schemaResolved?: MetadataSchema; + /** - * Method to print this metadata field as a string - * @param separator The separator between the schema, element and qualifier in the string + * Method to print this metadata field as a string without the schema + * @param separator The separator between element and qualifier in the string */ toString(separator: string = '.'): string { let key = this.element; @@ -79,6 +84,28 @@ export class MetadataField extends ListableObject implements HALResource { return key; } + /** + * Method to print this metadata field as a string + * @param separator The separator between the schema, element and qualifier in the string + */ + toStringWithSchema(separator: string = '.', schemaService: MetadataSchemaDataService): Observable { + let schemaObject: Observable> = this.schema; + if (!hasValue(this.schema)) { + schemaObject = schemaService.findByHref(this._links.schema.href); + } + return schemaObject.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + map((schemaPayload: MetadataSchema) => { + let key = this.element; + if (isNotEmpty(this.qualifier)) { + key += separator + this.qualifier; + } + return schemaPayload.namespace + separator + key; + }) + ) + } + /** * Method that returns as which type of object this object should be rendered */ diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index 72de6ec793..726ae4613d 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -297,13 +297,10 @@ export class RegistryService { /** * Retrieve a filtered paginated list of metadata fields - * @param query {string} The query to filter the field names by + * @param query {string} The query to use for the metadata field name, can be partial (e.g. "dc.ti") * @returns an observable that emits a remote data object with a page of metadata fields that match the query */ - // TODO this is temporarily disabled. The performance is too bad. - // Querying metadatafields will need to be implemented as a search endpoint on the rest api, - // not by downloading everything and preforming the query client side. - queryMetadataFields(query: string): Observable>> { - return createSuccessfulRemoteDataObject$(new PaginatedList(null, [])); + queryMetadataFields(query: string, options: FindListOptions = {}, ...linksToFollow: Array>): Observable>> { + return this.metadataFieldService.findByFieldName(null, null, null, query, options, ...linksToFollow); } }