71894: MD field suggestions on edit item page

This commit is contained in:
Marie Verdonck
2020-07-16 15:56:07 +02:00
parent cd6c5b70c8
commit 77b2506112
4 changed files with 106 additions and 23 deletions

View File

@@ -1,9 +1,13 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'; 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 { hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { RegistryService } from '../../../../core/registry/registry.service'; import { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; 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 { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; 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 { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { followLink } from '../../../../shared/utils/follow-link-config.model';
@Component({ @Component({
// tslint:disable-next-line:component-selector // tslint:disable-next-line:component-selector
@@ -60,6 +65,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
constructor( constructor(
private registryService: RegistryService, private registryService: RegistryService,
private objectUpdatesService: ObjectUpdatesService, private objectUpdatesService: ObjectUpdatesService,
private metadataSchemaService: MetadataSchemaDataService
) { ) {
} }
@@ -128,25 +134,53 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
*/ */
findMetadataFieldSuggestions(query: string): void { findMetadataFieldSuggestions(query: string): void {
if (isNotEmpty(query)) { if (isNotEmpty(query)) {
this.registryService.queryMetadataFields(query).pipe( this.registryService.queryMetadataFields(query, null, followLink('schema')).pipe(
// getSucceededRemoteData(), getSucceededRemoteData(),
take(1), take(1),
map((data) => data.payload.page) map((data) => data.payload.page),
).subscribe( switchMap((fields: MetadataField[]) => {
(fields: MetadataField[]) => this.metadataFieldSuggestions.next( return fields.map((field: MetadataField, index: number) => {
fields.map((field: MetadataField) => { // Resolve the metadata field's schema if not already the case, to be able to form complete MD field name
return { if (!hasValue(field.schemaResolved)) {
displayValue: field.toString().split('.').join('.​'), field.schema.pipe(
value: field.toString() 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 { } else {
this.metadataFieldSuggestions.next([]); 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 * 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 * @return an observable that emits true when the user should be able to edit this field and false when they should not

View File

@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { hasValue } from '../../shared/empty.util';
import { dataService } from '../cache/builders/build-decorators'; import { dataService } from '../cache/builders/build-decorators';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
@@ -27,6 +28,7 @@ import { RequestParam } from '../cache/models/request-param.model';
export class MetadataFieldDataService extends DataService<MetadataField> { export class MetadataFieldDataService extends DataService<MetadataField> {
protected linkPath = 'metadatafields'; protected linkPath = 'metadatafields';
protected searchBySchemaLinkPath = 'bySchema'; protected searchBySchemaLinkPath = 'bySchema';
protected searchByFieldNameLinkPath = 'byFieldName';
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,
@@ -53,6 +55,29 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, ...linksToFollow); 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<FollowLinkConfig<MetadataField>>) {
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 * Clear all metadata field requests
* Used for refreshing lists after adding/updating/removing a metadata field from a metadata schema * Used for refreshing lists after adding/updating/removing a metadata field from a metadata schema

View File

@@ -1,10 +1,13 @@
import { autoserialize, deserialize } from 'cerialize'; 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 { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { link, typedObject } from '../cache/builders/build-decorators'; import { link, typedObject } from '../cache/builders/build-decorators';
import { MetadataSchemaDataService } from '../data/metadata-schema-data.service';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { HALLink } from '../shared/hal-link.model'; import { HALLink } from '../shared/hal-link.model';
import { HALResource } from '../shared/hal-resource.model'; import { HALResource } from '../shared/hal-resource.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../shared/operators';
import { ResourceType } from '../shared/resource-type'; import { ResourceType } from '../shared/resource-type';
import { excludeFromEquals } from '../utilities/equals.decorators'; import { excludeFromEquals } from '../utilities/equals.decorators';
import { METADATA_FIELD } from './metadata-field.resource-type'; import { METADATA_FIELD } from './metadata-field.resource-type';
@@ -67,9 +70,11 @@ export class MetadataField extends ListableObject implements HALResource {
@link(METADATA_SCHEMA) @link(METADATA_SCHEMA)
schema?: Observable<RemoteData<MetadataSchema>>; schema?: Observable<RemoteData<MetadataSchema>>;
schemaResolved?: MetadataSchema;
/** /**
* Method to print this metadata field as a string * Method to print this metadata field as a string without the schema
* @param separator The separator between the schema, element and qualifier in the string * @param separator The separator between element and qualifier in the string
*/ */
toString(separator: string = '.'): string { toString(separator: string = '.'): string {
let key = this.element; let key = this.element;
@@ -79,6 +84,28 @@ export class MetadataField extends ListableObject implements HALResource {
return key; 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<string> {
let schemaObject: Observable<RemoteData<MetadataSchema>> = 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 * Method that returns as which type of object this object should be rendered
*/ */

View File

@@ -297,13 +297,10 @@ export class RegistryService {
/** /**
* Retrieve a filtered paginated list of metadata fields * 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 * @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. queryMetadataFields(query: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<MetadataField>>): Observable<RemoteData<PaginatedList<MetadataField>>> {
// Querying metadatafields will need to be implemented as a search endpoint on the rest api, return this.metadataFieldService.findByFieldName(null, null, null, query, options, ...linksToFollow);
// not by downloading everything and preforming the query client side.
queryMetadataFields(query: string): Observable<RemoteData<PaginatedList<MetadataField>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList<MetadataField>(null, []));
} }
} }