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 { 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

View File

@@ -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<MetadataField> {
protected linkPath = 'metadatafields';
protected searchBySchemaLinkPath = 'bySchema';
protected searchByFieldNameLinkPath = 'byFieldName';
constructor(
protected requestService: RequestService,
@@ -53,6 +55,29 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
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
* 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 { 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<RemoteData<MetadataSchema>>;
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<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
*/

View File

@@ -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<RemoteData<PaginatedList<MetadataField>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList<MetadataField>(null, []));
queryMetadataFields(query: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<MetadataField>>): Observable<RemoteData<PaginatedList<MetadataField>>> {
return this.metadataFieldService.findByFieldName(null, null, null, query, options, ...linksToFollow);
}
}