diff --git a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts index 315d3ff89e..d297e1e0af 100644 --- a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts +++ b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts @@ -7,6 +7,11 @@ import {typedObject} from '../../../core/cache/builders/build-decorators'; import {NotifyServicePattern} from './ldn-service-patterns.model'; +export interface LdnServiceByPattern { + allowsMultipleRequests: boolean; + services: LdnService[]; +} + /** An LdnService and its properties. */ @typedObject @inheritSerialization(CacheableObject) diff --git a/src/app/core/json-patch/builder/json-patch-operations-builder.ts b/src/app/core/json-patch/builder/json-patch-operations-builder.ts index f5a584fd3d..0982e3ed53 100644 --- a/src/app/core/json-patch/builder/json-patch-operations-builder.ts +++ b/src/app/core/json-patch/builder/json-patch-operations-builder.ts @@ -1,5 +1,6 @@ import { Store } from '@ngrx/store'; import { + FlushPatchOperationAction, NewPatchAddOperationAction, NewPatchMoveOperationAction, NewPatchRemoveOperationAction, @@ -99,6 +100,20 @@ export class JsonPatchOperationsBuilder { path.path)); } + /** + * Dispatches a new FlushPatchOperationAction + * + * @param path + * a JsonPatchOperationPathObject representing path + */ + flushOperation(path: JsonPatchOperationPathObject) { + this.store.dispatch( + new FlushPatchOperationAction( + path.rootElement, + path.subRootElement, + path.path)); + } + protected prepareValue(value: any, plain: boolean, first: boolean) { let operationValue: any = null; if (hasValue(value)) { diff --git a/src/app/core/json-patch/json-patch-operations.actions.ts b/src/app/core/json-patch/json-patch-operations.actions.ts index 6fea7a58fb..81cce174ec 100644 --- a/src/app/core/json-patch/json-patch-operations.actions.ts +++ b/src/app/core/json-patch/json-patch-operations.actions.ts @@ -20,6 +20,7 @@ export const JsonPatchOperationsActionTypes = { COMMIT_JSON_PATCH_OPERATIONS: type('dspace/core/patch/COMMIT_JSON_PATCH_OPERATIONS'), ROLLBACK_JSON_PATCH_OPERATIONS: type('dspace/core/patch/ROLLBACK_JSON_PATCH_OPERATIONS'), FLUSH_JSON_PATCH_OPERATIONS: type('dspace/core/patch/FLUSH_JSON_PATCH_OPERATIONS'), + FLUSH_JSON_PATCH_OPERATION: type('dspace/core/patch/FLUSH_JSON_PATCH_OPERATION'), START_TRANSACTION_JSON_PATCH_OPERATIONS: type('dspace/core/patch/START_TRANSACTION_JSON_PATCH_OPERATIONS'), DELETE_PENDING_JSON_PATCH_OPERATIONS: type('dspace/core/patch/DELETE_PENDING_JSON_PATCH_OPERATIONS'), }; @@ -120,6 +121,32 @@ export class FlushPatchOperationsAction implements Action { } } + +/** + * An ngrx action to flush a single operation of the JSON Patch operations + */ +export class FlushPatchOperationAction implements Action { + type = JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATION; + payload: { + resourceType: string; + resourceId: string; + path: string + }; + + /** + * Create a new FlushPatchOperationsAction + * + * @param resourceType + * the resource's type + * @param resourceId + * the resource's ID + * @param path + * the path of the operation + */ + constructor(resourceType: string, resourceId: string, path: string) { + this.payload = { resourceType, resourceId, path }; + } +} /** * An ngrx action to Add new HTTP/PATCH ADD operations to state */ @@ -284,4 +311,5 @@ export type PatchOperationsActions | NewPatchReplaceOperationAction | RollbacktPatchOperationsAction | StartTransactionPatchOperationsAction - | DeletePendingJsonPatchOperationsAction; + | DeletePendingJsonPatchOperationsAction + | FlushPatchOperationAction; diff --git a/src/app/core/json-patch/json-patch-operations.reducer.ts b/src/app/core/json-patch/json-patch-operations.reducer.ts index 5e00027edb..a3f972e190 100644 --- a/src/app/core/json-patch/json-patch-operations.reducer.ts +++ b/src/app/core/json-patch/json-patch-operations.reducer.ts @@ -12,7 +12,7 @@ import { CommitPatchOperationsAction, StartTransactionPatchOperationsAction, RollbacktPatchOperationsAction, - DeletePendingJsonPatchOperationsAction + DeletePendingJsonPatchOperationsAction, FlushPatchOperationAction } from './json-patch-operations.actions'; import { JsonPatchOperationModel, JsonPatchOperationType } from './json-patch.model'; @@ -71,7 +71,7 @@ export function jsonPatchOperationsReducer(state = initialState, action: PatchOp } case JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATIONS: { - return flushOperation(state, action as FlushPatchOperationsAction); + return flushOperations(state, action as FlushPatchOperationsAction); } case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION: { @@ -106,6 +106,10 @@ export function jsonPatchOperationsReducer(state = initialState, action: PatchOp return deletePendingOperations(state, action as DeletePendingJsonPatchOperationsAction); } + case JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATION: { + return flushOperation(state, action as FlushPatchOperationAction); + } + default: { return state; } @@ -197,6 +201,39 @@ function deletePendingOperations(state: JsonPatchOperationsState, action: Delete return null; } +/** + * Flush one operation from JsonPatchOperationsState. + * + * @param state + * the current state + * @param action + * an FlushPatchOperationsAction + * @return JsonPatchOperationsState + * the new state. + */ +function flushOperation(state: JsonPatchOperationsState, action: FlushPatchOperationAction): JsonPatchOperationsState { + const payload = action.payload; + if (state[payload.resourceType] && state[payload.resourceType].children) { + const body = state[payload.resourceType].children[payload.resourceId].body; + const operation = body.filter(operations => operations.operation.path === payload.path)[0]; + const operationIndex = body.indexOf(operation); + const newBody = [...body]; + newBody.splice(operationIndex, 1); + + return Object.assign({}, state, { + [action.payload.resourceType]: Object.assign({}, { + children: { + [action.payload.resourceId]: { + body: newBody, + } + }, + }) + }); + } else { + return state; + } +} + /** * Add new JSON patch operation list. * @@ -273,7 +310,7 @@ function hasValidBody(state: JsonPatchOperationsState, resourceType: any, resour * @return SubmissionObjectState * the new state, with the section new validity status. */ -function flushOperation(state: JsonPatchOperationsState, action: FlushPatchOperationsAction): JsonPatchOperationsState { +function flushOperations(state: JsonPatchOperationsState, action: FlushPatchOperationsAction): JsonPatchOperationsState { if (hasValue(state[ action.payload.resourceType ])) { let newChildren; if (isNotUndefined(action.payload.resourceId)) { diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html index e3bdffd065..7b61b68564 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.html @@ -1,115 +1,138 @@
-
+
-
-
-
-
- - -
- -
-
- - {{'submission.section.section-coar-notify.small.notification' | translate : {pattern : pattern} }} - - - - {{ error.message | translate}} - - +
-
- -
-
{{ 'submission.section.section-coar-notify.selection.description' | translate }}
-
- {{ ldnServiceByPattern[pattern][serviceIndex].description }} +
+
+
+ +
- + +
+ +
+ + + {{'submission.section.section-coar-notify.small.notification' | translate : {pattern : ldnPattern.pattern} }} + + + + {{ error.message | translate}} + + +
+
+ +
+
{{ 'submission.section.section-coar-notify.selection.description' | translate }}
+
+ {{ ldnServiceByPattern[ldnPattern.pattern].services[serviceIndex].description }} +
+ {{ 'submission.section.section-coar-notify.selection.no-description' | translate }} - + +
-
-
-
-
+
+
+
{{ 'submission.section.section-coar-notify.notification.error' | translate }} +
+
+
+
+
@@ -118,7 +141,7 @@

- {{'submission.section.section-coar-notify.info.no-pattern' | translate }} + {{ 'submission.section.section-coar-notify.info.no-pattern' | translate }}

diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss index 3ac8827f74..4a3e7072a3 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.scss @@ -2,3 +2,4 @@ @import '../../../shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.scss'; @import '../../../shared/form/form.component.scss'; + diff --git a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts index 92f56b4f83..6e5925985b 100644 --- a/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts +++ b/src/app/submission/sections/section-coar-notify/section-coar-notify.component.ts @@ -8,15 +8,19 @@ import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/jso import { SectionsService } from '../sections.service'; import { SectionDataObject } from '../models/section-data.model'; -import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from '../../../core/shared/operators'; import { LdnServicesService } from '../../../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service'; -import { LdnService } from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; +import { + LdnService, + LdnServiceByPattern +} from '../../../admin/admin-ldn-services/ldn-services-model/ldn-services.model'; import { CoarNotifyConfigDataService } from './coar-notify-config-data.service'; import { filter, map, take, tap } from 'rxjs/operators'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { SubmissionSectionError } from '../../objects/submission-section-error.model'; +import { LdnPattern } from './submission-coar-notify.config'; /** * This component represents a section that contains the submission section-coar-notify form. @@ -33,12 +37,12 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent /** * Contains an array of string patterns. */ - patterns: string[] = []; + patterns: LdnPattern[] = []; /** * An object that maps string keys to arrays of LdnService objects. * Used to store LdnService objects by pattern. */ - ldnServiceByPattern: { [key: string]: LdnService[] } = {}; + ldnServiceByPattern: { [key: string]: LdnServiceByPattern } = {}; /** * A map representing all services for each pattern * { @@ -50,7 +54,7 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent * @type {{ [key: string]: {[key: number]: number} }} * @memberof SubmissionSectionCoarNotifyComponent */ - previousServices: { [key: string]: {[key: number]: number} } = {}; + previousServices: { [key: string]: LdnServiceByPattern } = {}; /** * The [[JsonPatchOperationPathCombiner]] object @@ -109,43 +113,39 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent * Handles the change event of a select element. * @param pattern - The pattern of the select element. * @param index - The index of the select element. + * @param selectedService - The selected LDN service. */ onChange(pattern: string, index: number, selectedService: LdnService | null) { // do nothing if the selected value is the same as the previous one - if (this.ldnServiceByPattern[pattern][index]?.id === selectedService?.id) { + if (this.ldnServiceByPattern[pattern].services[index]?.id === selectedService?.id) { return; } // initialize the previousServices object for the pattern if it does not exist if (!this.previousServices[pattern]) { - this.previousServices[pattern] = {}; + this.previousServices[pattern] = { + services: [], + allowsMultipleRequests: this.patterns[pattern]?.multipleRequest + }; } - if (hasNoValue(selectedService)) { - // on value change, remove the path when the selected value is null - // and remove the previous value stored for the same index and pattern - this.operationsBuilder.remove(this.pathCombiner.getPath([pattern, index.toString()])); - this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); - this.ldnServiceByPattern[pattern][index] = null; - this.previousServices[pattern][index] = null; - return; - } // store the previous value - this.previousServices[pattern][index] = this.ldnServiceByPattern[pattern][index]?.id; + this.previousServices[pattern].services[index] = this.ldnServiceByPattern[pattern].services[index]; // set the new value - this.ldnServiceByPattern[pattern][index] = selectedService; + this.ldnServiceByPattern[pattern].services[index] = selectedService; - const hasPrevValueStored = hasValue(this.previousServices[pattern][index]) && this.previousServices[pattern][index] !== selectedService.id; + const hasPrevValueStored = hasValue(this.previousServices[pattern].services[index]) && this.previousServices[pattern].services[index].id !== selectedService?.id; if (hasPrevValueStored) { - // replace the path // when there is a previous value stored and it is different from the new one - this.operationsBuilder.replace(this.pathCombiner.getPath([pattern, index.toString()]), selectedService.id, true); - } else { + this.operationsBuilder.flushOperation(this.pathCombiner.getPath([pattern, '-'])); + } + + if (!hasPrevValueStored || (selectedService?.id && hasPrevValueStored)) { // add the path when there is no previous value stored this.operationsBuilder.add(this.pathCombiner.getPath([pattern, '-']), [selectedService.id], false, true); } // set the previous value to the new value - this.previousServices[pattern][index] = this.ldnServiceByPattern[pattern][index].id; + this.previousServices[pattern].services[index] = this.ldnServiceByPattern[pattern].services[index]; this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); this.chd.detectChanges(); } @@ -158,48 +158,60 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent * so that the select element is initialized with a null value and to display the default select input. */ initSelectedServicesByPattern(): void { - this.patterns.forEach((pattern) => { - if (hasValue(this.sectionData.data[pattern])) { + this.patterns.forEach((ldnPattern) => { + if (hasValue(this.sectionData.data[ldnPattern.pattern])) { this.subs.push( - this.filterServices(pattern) + this.filterServices(ldnPattern.pattern) .subscribe((services: LdnService[]) => { - const selectedServices = services.filter((service) => { - const selection = (this.sectionData.data[pattern] as LdnService[]).find((s: LdnService) => s.id === service.id); - this.addService(pattern, selection); - return this.sectionData.data[pattern].includes(service.id); + this.ldnServiceByPattern[ldnPattern.pattern].services = services.filter((service) => { + const selection = (this.sectionData.data[ldnPattern.pattern] as LdnService[]).find((s: LdnService) => s.id === service.id); + this.addService(ldnPattern, selection); + return this.sectionData.data[ldnPattern.pattern].includes(service.id); }); - this.ldnServiceByPattern[pattern] = selectedServices; }) ); } else { - this.ldnServiceByPattern[pattern] = []; - this.addService(pattern, null); + this.ldnServiceByPattern[ldnPattern.pattern] = { + services: [], + allowsMultipleRequests: ldnPattern.multipleRequest + }; + this.addService(ldnPattern, null); } }); } /** * Adds a new service to the selected services for the given pattern. - * @param pattern - The pattern to add the new service to. + * @param ldnPattern - The pattern to add the new service to. * @param newService - The new service to add. */ - addService(pattern: string, newService: LdnService) { + addService(ldnPattern: LdnPattern, newService: LdnService) { // Your logic to add a new service to the selected services for the pattern // Example: Push the newService to the array corresponding to the pattern - if (!this.ldnServiceByPattern[pattern]) { - this.ldnServiceByPattern[pattern] = []; + if (!this.ldnServiceByPattern[ldnPattern.pattern]) { + this.ldnServiceByPattern[ldnPattern.pattern] = { + services: [], + allowsMultipleRequests: ldnPattern.multipleRequest + }; } - this.ldnServiceByPattern[pattern].push(newService); + this.ldnServiceByPattern[ldnPattern.pattern].services.push(newService); } /** * Removes the service at the specified index from the array corresponding to the pattern. - * (part of next phase of implementation) + * @param ldnPattern - The LDN pattern from which to remove the service + * @param serviceIndex - the service index to remove */ - removeService(pattern: string, serviceIndex: number) { - if (this.ldnServiceByPattern[pattern]) { + removeService(ldnPattern: LdnPattern, serviceIndex: number) { + if (this.ldnServiceByPattern[ldnPattern.pattern]) { // Remove the service at the specified index from the array - this.ldnServiceByPattern[pattern].splice(serviceIndex, 1); + this.ldnServiceByPattern[ldnPattern.pattern].services.splice(serviceIndex, 1); + this.previousServices[ldnPattern.pattern]?.services.splice(serviceIndex, 1); + this.operationsBuilder.flushOperation(this.pathCombiner.getPath([ldnPattern.pattern, '-'])); + this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionData.id); + } + if (!this.ldnServiceByPattern[ldnPattern.pattern].services.length) { + this.addNewService(ldnPattern); } } @@ -290,4 +302,13 @@ export class SubmissionSectionCoarNotifyComponent extends SectionModelComponent .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); } + + /** + * Add new row to dropdown for multiple service selection + * @param ldnPattern - the related LDN pattern where the service is added + */ + addNewService(ldnPattern: LdnPattern): void { + //idle new service for new selection + this.ldnServiceByPattern[ldnPattern.pattern].services.push(null); + } } diff --git a/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts b/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts index 04973f80c8..4a8116cdde 100644 --- a/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts +++ b/src/app/submission/sections/section-coar-notify/submission-coar-notify.config.ts @@ -6,7 +6,10 @@ import { excludeFromEquals } from '../../../core/utilities/equals.decorators'; import { typedObject } from '../../../core/cache/builders/build-decorators'; import { SUBMISSION_COAR_NOTIFY_CONFIG } from './section-coar-notify-service.resource-type'; - +export interface LdnPattern { + pattern: string, + multipleRequest: boolean +} /** A SubmissionCoarNotifyConfig and its properties. */ @typedObject @inheritSerialization(CacheableObject) @@ -24,7 +27,7 @@ export class SubmissionCoarNotifyConfig extends CacheableObject { uuid: string; @autoserialize - patterns: string[]; + patterns: LdnPattern[]; @deserialize _links: {