fix a number of issues with name variants

This commit is contained in:
Art Lowel
2020-04-03 17:28:13 +02:00
parent 62002f94ef
commit f64eff42f4
13 changed files with 116 additions and 62 deletions

View File

@@ -44,11 +44,12 @@ import {
FindByIDRequest,
FindListOptions,
FindListRequest,
GetRequest, PatchRequest
GetRequest, PatchRequest, PutRequest
} from './request.models';
import { RequestEntry } from './request.reducer';
import { RequestService } from './request.service';
import { RestRequestMethod } from './rest-request-method';
import { GenericConstructor } from '../shared/generic-constructor';
export abstract class DataService<T extends CacheableObject> {
protected abstract requestService: RequestService;
@@ -307,24 +308,23 @@ export abstract class DataService<T extends CacheableObject> {
* Return an observable that emits response from the server
*/
searchBy(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
const requestId = this.requestService.generateRequestId();
const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow);
return hrefObs.pipe(
find((href: string) => hasValue(href)),
tap((href: string) => {
this.requestService.removeByHrefSubstring(href);
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
request.responseMsToLive = 10 * 1000;
hrefObs.pipe(
find((href: string) => hasValue(href))
).subscribe((href: string) => {
this.requestService.removeByHrefSubstring(href);
const request = new FindListRequest(requestId, href, options);
request.responseMsToLive = 10 * 1000;
this.requestService.configure(request);
});
this.requestService.configure(request);
}
return this.requestService.getByUUID(requestId).pipe(
find((requestEntry) => hasValue(requestEntry) && requestEntry.completed),
switchMap((requestEntry) =>
this.rdbService.buildList<T>(requestEntry.request.href, ...linksToFollow)
),
switchMap((href) => this.requestService.getByHref(href)),
skipWhile((requestEntry) => hasValue(requestEntry) && requestEntry.completed),
switchMap((href) =>
this.rdbService.buildList<T>(hrefObs, ...linksToFollow) as Observable<RemoteData<PaginatedList<T>>>
)
);
}
@@ -353,6 +353,23 @@ export abstract class DataService<T extends CacheableObject> {
);
}
/**
* Send a PUT request for the specified object
*
* @param object The object to send a put request for.
*/
put(object: T): Observable<RemoteData<T>> {
const requestId = this.requestService.generateRequestId();
const serializedObject = new DSpaceSerializer(object.constructor as GenericConstructor<{}>).serialize(object);
const request = new PutRequest(requestId, object._links.self.href, serializedObject);
this.requestService.configure(request);
return this.requestService.getByUUID(requestId).pipe(
find((request: RequestEntry) => hasValue(request) && request.completed),
switchMap(() => this.findByHref(object._links.self.href))
);
}
/**
* Add a new patch to the object cache
* The patch is derived from the differences between the given object and its version in the object cache

View File

@@ -1,11 +1,12 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store';
import { combineLatest as observableCombineLatest } from 'rxjs';
import { combineLatest as observableCombineLatest, OperatorFunction } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import {
distinctUntilChanged,
filter,
find,
map,
startWith,
switchMap,
@@ -44,7 +45,7 @@ import {
configureRequest, getFirstSucceededRemoteDataPayload,
getRemoteDataPayload,
getResponseFromEntry,
getSucceededRemoteData
getSucceededRemoteData, getPaginatedListPayload
} from '../shared/operators';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
@@ -53,6 +54,7 @@ import { PaginatedList } from './paginated-list';
import { RemoteData, RemoteDataState } from './remote-data';
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
import { RequestService } from './request.service';
import has = Reflect.has;
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
@@ -167,7 +169,7 @@ 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 refreshRelationshipItemsInCache(item) {
public refreshRelationshipItemsInCache(item) {
this.objectCache.remove(item._links.self.href);
this.requestService.removeByHrefSubstring(item.uuid);
observableCombineLatest(
@@ -310,24 +312,29 @@ export class RelationshipService extends DataService<Relationship> {
* @param label The rightward or leftward type of the relationship
*/
getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string, options?: FindListOptions): Observable<Relationship> {
return this.getItemRelationshipsByLabel(item1, label, options, followLink('relationshipType'), followLink('leftItem'), followLink('rightItem'))
.pipe(
console.log('getRelationshipByItemsAndLabel', item1, item2, label, options);
return this.getItemRelationshipsByLabel(
item1,
label,
options,
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem')
).pipe(
getSucceededRemoteData(),
isNotEmptyOperator(),
map((relationshipListRD: RemoteData<PaginatedList<Relationship>>) => relationshipListRD.payload.page),
mergeMap((relationships: Relationship[]) => {
return observableCombineLatest(...relationships.map((relationship: Relationship) => {
return observableCombineLatest(
this.isItemMatchWithItemRD(this.itemService.findByHref(relationship._links.leftItem.href), item2),
this.isItemMatchWithItemRD(this.itemService.findByHref(relationship._links.rightItem.href), item2)
).pipe(
map(([isLeftItem, isRightItem]) => isLeftItem || isRightItem),
map((isMatch) => isMatch ? relationship : undefined)
);
})
)
// the mergemap below will emit all elements of the list as separate events
mergeMap((relationshipListRD: RemoteData<PaginatedList<Relationship>>) => relationshipListRD.payload.page),
mergeMap((relationship: Relationship) => {
return observableCombineLatest([
this.isItemMatchWithItemRD(this.itemService.findByHref(relationship._links.leftItem.href), item2),
this.isItemMatchWithItemRD(this.itemService.findByHref(relationship._links.rightItem.href), item2)
]).pipe(
map(([isLeftItem, isRightItem]) => isLeftItem || isRightItem),
map((isMatch) => isMatch ? relationship : undefined)
);
}),
map((relationships: Relationship[]) => relationships.find(((relationship) => hasValue(relationship))))
filter((relationship) => hasValue(relationship)),
take(1)
)
}
@@ -384,11 +391,8 @@ export class RelationshipService extends DataService<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>> {
let count = 0
const update$: Observable<RemoteData<Relationship>> = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
return this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
.pipe(
tap((v) => console.log('updateNameVariant after getRelationshipByItemsAndLabel', v)),
filter((relation: Relationship) => hasValue(relation)),
switchMap((relation: Relationship) =>
relation.relationshipType.pipe(
getSucceededRemoteData(),
@@ -399,7 +403,6 @@ export class RelationshipService extends DataService<Relationship> {
)
),
switchMap((relationshipAndType: { relation: Relationship, type: RelationshipType }) => {
console.log('updateNameVariant switchMap', relationshipAndType);
const { relation, type } = relationshipAndType;
let updatedRelationship;
if (relationshipLabel === type.leftwardType) {
@@ -409,15 +412,7 @@ export class RelationshipService extends DataService<Relationship> {
}
return this.update(updatedRelationship);
}),
tap((relationshipRD: RemoteData<Relationship>) => {
if (relationshipRD.hasSucceeded && count < 1) {
count++;
this.refreshRelationshipItemsInCache(item1);
this.refreshRelationshipItemsInCache(item2);
}
})
);
return update$
}
/**

View File

@@ -1,6 +1,7 @@
form {
z-index: 1;
&:before {
pointer-events: none; // prevent the icon from catching the click
position: absolute;
font-weight: 900;
font-family: "Font Awesome 5 Free";
@@ -15,4 +16,4 @@ form {
input.suggestion_input {
background: transparent;
}
}
}

View File

@@ -55,7 +55,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
this.relationshipService.getNameVariant(this.listID, this.dso.uuid)
.pipe(take(1))
.subscribe((nameVariant: string) => {
this.selectedName = nameVariant || defaultValue;
this.selectedName = nameVariant || defaultValue;
}
);
}

View File

@@ -1,5 +1,6 @@
form {
&:before {
pointer-events: none; // prevent the icon from catching the click
position: absolute;
font-weight: 900;
font-family: "Font Awesome 5 Free";

View File

@@ -83,7 +83,12 @@ import { SelectableListService } from '../../../object-list/selectable-list/sele
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
import { getAllSucceededRemoteData, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
import {
getAllSucceededRemoteData,
getRemoteDataPayload,
getSucceededRemoteData,
getAllSucceededRemoteDataPayload, getPaginatedListPayload, getFirstSucceededRemoteDataPayload
} from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
@@ -95,11 +100,13 @@ import { PaginatedList } from '../../../../core/data/paginated-list';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { Collection } from '../../../../core/shared/collection.model';
import { MetadataValue } from '../../../../core/shared/metadata.models';
import { MetadataValue, VIRTUAL_METADATA_PREFIX } from '../../../../core/shared/metadata.models';
import { FormService } from '../../form.service';
import { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
import { SubmissionService } from '../../../../submission/submission.service';
import { followLink } from '../../../utils/follow-link-config.model';
import { paginatedRelationsToItems } from '../../../../+item-page/simple/item-types/shared/item-relationships-utils';
import { RelationshipOptions } from '../models/relationship-options.model';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
switch (model.type) {
@@ -261,13 +268,37 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
}
if (this.model.relationshipConfig) {
const relationshipOptions = Object.assign(new RelationshipOptions(), this.model.relationshipConfig);
this.listId = 'list-' + this.model.relationshipConfig.relationshipType;
this.setItem();
const subscription = this.selectableListService.getSelectableList(this.listId).pipe(
find((list: SelectableListState) => hasNoValue(list)),
switchMap(() => this.item$.pipe(take(1))),
switchMap((item) => {
return this.relationshipService.getRelatedItemsByLabel(item, this.model.relationshipConfig.relationshipType).pipe(
const relationshipsRD$ = this.relationshipService.getItemRelationshipsByLabel(item,
this.model.relationshipConfig.relationshipType,
undefined,
followLink('leftItem'),
followLink('rightItem'),
followLink('relationshipType')
);
relationshipsRD$.pipe(
getFirstSucceededRemoteDataPayload(),
getPaginatedListPayload()
).subscribe((relationships: Relationship[]) => {
// set initial namevariants for pre-existing relationships
relationships.forEach((relationship: Relationship) => {
const relationshipMD: MetadataValue = item.firstMetadata(relationshipOptions.metadataField, { authority: `${VIRTUAL_METADATA_PREFIX}${relationship.id}` });
const nameVariantMD: MetadataValue = item.firstMetadata(this.model.metadataFields, { authority: `${VIRTUAL_METADATA_PREFIX}${relationship.id}` });
if (hasValue(relationshipMD) && isNotEmpty(relationshipMD.value) && hasValue(nameVariantMD) && isNotEmpty(nameVariantMD.value)) {
this.relationshipService.setNameVariant(this.listId, relationshipMD.value, nameVariantMD.value);
}
});
});
return relationshipsRD$.pipe(
paginatedRelationsToItems(item.uuid),
getSucceededRemoteData(),
map((items: RemoteData<PaginatedList<Item>>) => items.payload.page.map((i) => Object.assign(new ItemSearchResult(), { indexableObject: i }))),
)

View File

@@ -208,6 +208,7 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges,
const relationMD: MetadataValue = this.submissionItem.firstMetadata(this.relationshipOptions.metadataField, { value: this.relatedItem.uuid });
if (hasValue(relationMD)) {
const metadataRepresentationMD: MetadataValue = this.submissionItem.firstMetadata(this.metadataFields, { authority: relationMD.authority });
const nextValue = Object.assign(
new ItemMetadataRepresentation(metadataRepresentationMD),
this.relatedItem

View File

@@ -7,6 +7,7 @@ export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig
submissionId: string;
relationshipConfig: RelationshipOptions;
metadataKey: string;
metadataFields: string[];
}
export class DynamicRowArrayModel extends DynamicFormArrayModel {
@@ -15,6 +16,7 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
@serializable() submissionId: string;
@serializable() relationshipConfig: RelationshipOptions;
@serializable() metadataKey: string;
@serializable() metadataFields: string[];
isRowArray = true;
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
@@ -24,5 +26,6 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
this.submissionId = config.submissionId;
this.relationshipConfig = config.relationshipConfig;
this.metadataKey = config.metadataKey;
this.metadataFields = config.metadataFields;
}
}

View File

@@ -20,6 +20,7 @@ import { RequestService } from '../../../../../core/data/request.service';
import { ServerSyncBufferActionTypes } from '../../../../../core/cache/server-sync-buffer.actions';
import { CommitPatchOperationsAction, JsonPatchOperationsActionTypes, PatchOperationsActions } from '../../../../../core/json-patch/json-patch-operations.actions';
import { followLink } from '../../../../utils/follow-link-config.model';
import { RemoteData } from '../../../../../core/data/remote-data';
const DEBOUNCE_TIME = 500;
@@ -101,10 +102,14 @@ export class RelationshipEffects {
if (inProgress) {
this.nameVariantUpdates[identifier] = nameVariant;
} else {
this.relationshipService.updateNameVariant(item1, item2, relationshipType, nameVariant).pipe(take(1))
.subscribe(() => {
this.updateAfterPatchSubmissionId = submissionId;
});
this.relationshipService.updateNameVariant(item1, item2, relationshipType, nameVariant).pipe(
filter((relationshipRD: RemoteData<Relationship>) => hasValue(relationshipRD.payload)),
take(1)
).subscribe(() => {
this.updateAfterPatchSubmissionId = submissionId;
this.relationshipService.refreshRelationshipItemsInCache(item1);
this.relationshipService.refreshRelationshipItemsInCache(item2);
});
}
}
)
@@ -161,8 +166,6 @@ export class RelationshipEffects {
private removeRelationship(item1: Item, item2: Item, relationshipType: string, submissionId: string) {
this.relationshipService.getRelationshipByItemsAndLabel(item1, item2, relationshipType).pipe(
take(1),
hasValueOperator(),
mergeMap((relationship: Relationship) => this.relationshipService.deleteRelationship(relationship.id, 'none')),
take(1),
switchMap(() => this.refreshWorkspaceItemInCache(submissionId)),

View File

@@ -268,7 +268,8 @@ describe('FormBuilderService test suite', () => {
];
},
required: false,
metadataKey: 'dc.contributor.author'
metadataKey: 'dc.contributor.author',
metadataFields: ['dc.contributor.author']
},
),
];

View File

@@ -1,4 +1,4 @@
const RELATION_METADATA_PREFIX = 'relation.'
const RELATION_METADATA_PREFIX = 'relation.';
/**
* The submission options for fields that can represent relationships

View File

@@ -55,6 +55,7 @@ export abstract class FieldParser {
required: JSON.parse( this.configData.mandatory),
submissionId: this.submissionId,
metadataKey,
metadataFields: this.getAllFieldIds(),
groupFactory: () => {
let model;
let isFirstModelInArray = true;

View File

@@ -10,7 +10,6 @@ import { AuthorityValue } from '../../core/integration/models/authority.value';
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
import { DynamicRowGroupModel } from '../form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { FormRowModel } from '../../core/config/models/config-submission-form.model';
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
export const qualdropSelectConfig = {
name: 'dc.identifier_QUALDROP_METADATA',
@@ -82,7 +81,8 @@ const rowArrayQualdropConfig = {
},
required: false,
submissionId: '1234',
metadataKey: 'dc.some.key'
metadataKey: 'dc.some.key',
metadataFields: ['dc.some.key']
} as DynamicRowArrayModelConfig;
export const MockRowArrayQualdropModel: DynamicRowArrayModel = new DynamicRowArrayModel(rowArrayQualdropConfig);