diff --git a/src/app/core/submission/workflowitem-data.service.spec.ts b/src/app/core/submission/workflowitem-data.service.spec.ts index f8a61c8647d..d4565dfea1f 100644 --- a/src/app/core/submission/workflowitem-data.service.spec.ts +++ b/src/app/core/submission/workflowitem-data.service.spec.ts @@ -12,6 +12,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { ObjectCacheService } from '../cache/object-cache.service'; import { RestResponse } from '../cache/response.models'; import { CoreState } from '../core-state.model'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { HrefOnlyDataService } from '../data/href-only-data.service'; import { RequestService } from '../data/request.service'; import { RequestEntry } from '../data/request-entry.model'; @@ -91,6 +92,7 @@ describe('WorkflowItemDataService test', () => { objectCache, halService, notificationsService, + new DefaultChangeAnalyzer(), ); } diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 6acbcc92852..b227e4c2bdf 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; +import { RestRequestMethod } from '@dspace/config/rest-request-method'; import { hasValue } from '@dspace/shared/utils/empty.util'; +import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; import { find, @@ -14,10 +16,15 @@ import { DeleteDataImpl, } from '../data/base/delete-data'; import { IdentifiableDataService } from '../data/base/identifiable-data.service'; +import { + PatchData, + PatchDataImpl, +} from '../data/base/patch-data'; import { SearchData, SearchDataImpl, } from '../data/base/search-data'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { FindListOptions } from '../data/find-list-options.model'; import { PaginatedList } from '../data/paginated-list.model'; import { RemoteData } from '../data/remote-data'; @@ -30,16 +37,27 @@ import { NoContent } from '../shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { WorkflowItem } from './models/workflowitem.model'; +/** + * Constructs an endpoint taking into account that the WorkflowItem's "uuid" has a prefix by removing that prefix from the ID + * @param endpoint Endpoint to append ID to + * @param resourceID WorkflowItem's "uuid" including the prefix to remove + */ +const constructWorkflowItemIdEndpoint = (endpoint, resourceID) => { + const regex = new RegExp(`^${WorkflowItem.type.value}-`); + return `${endpoint}/${resourceID.replace(regex, '')}`; +}; + /** * A service that provides methods to make REST requests with workflow items endpoint. */ @Injectable({ providedIn: 'root' }) -export class WorkflowItemDataService extends IdentifiableDataService implements SearchData, DeleteData { +export class WorkflowItemDataService extends IdentifiableDataService implements SearchData, DeleteData, PatchData { protected searchByItemLinkPath = 'item'; protected responseMsToLive = 10 * 1000; private searchData: SearchDataImpl; private deleteData: DeleteDataImpl; + private patchData: PatchDataImpl; constructor( protected requestService: RequestService, @@ -47,11 +65,13 @@ export class WorkflowItemDataService extends IdentifiableDataService, ) { super('workflowitems', requestService, rdbService, objectCache, halService); this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.comparator, this.responseMsToLive, constructWorkflowItemIdEndpoint); } /** @@ -148,4 +168,20 @@ export class WorkflowItemDataService extends IdentifiableDataService> { return this.deleteData.deleteByHref(href, copyVirtualMetadata); } + + commitUpdates(method?: RestRequestMethod): void { + this.patchData.commitUpdates(method); + } + + createPatchFromCache(object: WorkflowItem): Observable { + return this.patchData.createPatchFromCache(object); + } + + patch(object: WorkflowItem, operations: Operation[]): Observable> { + return this.patchData.patch(object, operations); + } + + update(object: WorkflowItem): Observable> { + return this.patchData.update(object); + } } diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index d8125c1c2a8..46df6094e98 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -3,8 +3,10 @@ import { HttpHeaders, } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { RestRequestMethod } from '@dspace/config/rest-request-method'; import { hasValue } from '@dspace/shared/utils/empty.util'; import { Store } from '@ngrx/store'; +import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; import { find, @@ -20,6 +22,10 @@ import { DeleteDataImpl, } from '../data/base/delete-data'; import { IdentifiableDataService } from '../data/base/identifiable-data.service'; +import { + PatchData, + PatchDataImpl, +} from '../data/base/patch-data'; import { SearchData, SearchDataImpl, @@ -37,15 +43,26 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NoContent } from '../shared/NoContent.model'; import { WorkspaceItem } from './models/workspaceitem.model'; +/** + * Constructs an endpoint taking into account that the WorkspaceItem's "uuid" has a prefix by removing that prefix from the ID + * @param endpoint Endpoint to append ID to + * @param resourceID WorkspaceItem's "uuid" including the prefix to remove + */ +const constructWorkspaceItemIdEndpoint = (endpoint, resourceID) => { + const regex = new RegExp(`^${WorkspaceItem.type.value}-`); + return `${endpoint}/${resourceID.replace(regex, '')}`; +}; + /** * A service that provides methods to make REST requests with workspaceitems endpoint. */ @Injectable({ providedIn: 'root' }) -export class WorkspaceitemDataService extends IdentifiableDataService implements DeleteData, SearchData{ +export class WorkspaceitemDataService extends IdentifiableDataService implements DeleteData, SearchData, PatchData { protected linkPath = 'workspaceitems'; protected searchByItemLinkPath = 'item'; private deleteData: DeleteData; private searchData: SearchDataImpl; + private patchData: PatchDataImpl; constructor( protected comparator: DSOChangeAnalyzer, @@ -55,10 +72,12 @@ export class WorkspaceitemDataService extends IdentifiableDataService) { + protected store: Store, + ) { super('workspaceitems', requestService, rdbService, objectCache, halService); this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.comparator, this.responseMsToLive, constructWorkspaceItemIdEndpoint); } public delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { return this.deleteData.delete(objectId, copyVirtualMetadata); @@ -137,4 +156,21 @@ export class WorkspaceitemDataService extends IdentifiableDataService[]): Observable>> { return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + + commitUpdates(method?: RestRequestMethod): void { + this.patchData.commitUpdates(method); + } + + createPatchFromCache(object: WorkspaceItem): Observable { + return this.patchData.createPatchFromCache(object); + } + + patch(object: WorkspaceItem, operations: Operation[]): Observable> { + return this.patchData.patch(object, operations); + } + + update(object: WorkspaceItem): Observable> { + return this.patchData.update(object); + } + } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts index 8fb617d0336..b76f4567996 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts @@ -276,6 +276,7 @@ describe('DsDynamicFormControlContainerComponent test suite', () => { { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn }, { provide: LiveRegionService, useValue: getLiveRegionServiceStub() }, { provide: Actions, useValue: actions$ }, + { provide: 'sectionDataProvider', useValue: { id: 'mock-section-id' } }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents().then(() => { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index 3bdd5d67f44..d6d888c9c6c 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -19,6 +19,7 @@ import { OnChanges, OnDestroy, OnInit, + Optional, Output, PLATFORM_ID, QueryList, @@ -124,6 +125,7 @@ import { import { AppState } from '../../../../app.reducer'; import { EditMetadataSecurityComponent } from '../../../../item-page/edit-item-page/edit-metadata-security/edit-metadata-security.component'; import { SubmissionObjectActionTypes } from '../../../../submission/objects/submission-objects.actions'; +import { SectionDataObject } from '../../../../submission/sections/models/section-data.model'; import { SubmissionService } from '../../../../submission/submission.service'; import { SubmissionObjectService } from '../../../../submission/submission-object.service'; import { LiveRegionService } from '../../../live-region/live-region.service'; @@ -174,6 +176,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo @Input() hasErrorMessaging = false; @Input() layout = null as DynamicFormLayout; @Input() model: any; + @Input() arrayIndex: number; securityLevel: number; relationshipValue$: Observable; isRelationship: boolean; @@ -235,6 +238,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo private actions$: Actions, protected renderer: Renderer2, @Inject(PLATFORM_ID) protected platformId: string, + @Optional() @Inject('sectionDataProvider') public sectionData: SectionDataObject, ) { super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService); this.fetchThumbnail = this.appConfig.browseBy.showThumbnails; @@ -485,6 +489,13 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo } else if (typeof this.model.value.value === 'string') { modalComp.query = this.model.value.value; } + + // If the existing value is not virtual, store properties on the modal required to perform a replace operation + if (hasValue(this.sectionData) && !this.model.value.isVirtual) { + modalComp.replaceValuePlace = this.arrayIndex || 0; + modalComp.replaceValueMetadataField = this.model.name; + modalComp.replaceValueSection = this.sectionData?.id; + } } modalComp.repeatable = this.model.repeatable; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html index 73cae52db2e..dd92d28bb21 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html @@ -40,6 +40,7 @@ [class.d-none]="_model.hidden" [layout]="formLayout" [model]="_model" + [arrayIndex]="idx" [templates]="templates" [ngClass]="[getClass('element', 'host', _model), getClass('grid', 'host', _model)]" (dfBlur)="onBlur($event)" diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.spec.ts index 1c808b0ba84..aae47ed3a4b 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.spec.ts @@ -69,6 +69,7 @@ describe('DsDynamicFormArrayComponent', () => { { provide: APP_CONFIG, useValue: environment }, { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn }, { provide: LiveRegionService, useValue: getLiveRegionServiceStub() }, + { provide: 'sectionDataProvider', useValue: { id: 'mock-section-id' } }, ], }).overrideComponent(DsDynamicFormArrayComponent, { remove: { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts index 0a40950997b..fa44cf3b539 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts @@ -255,6 +255,7 @@ describe('DsDynamicRelationGroupComponent test suite', () => { { provide: APP_DATA_SERVICES_MAP, useValue: {} }, { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn }, { provide: LiveRegionService, useValue: getLiveRegionServiceStub() }, + { provide: 'sectionDataProvider', useValue: { id: 'mock-section-id' } }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html index 9e2a8fe5137..8e698d2c103 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html @@ -5,10 +5,10 @@