diff --git a/resources/i18n/en.json b/resources/i18n/en.json index c2c8d8c464..7a6137559a 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -299,11 +299,11 @@ "submit.progressbar.describe.stepone": "Describe", "submit.progressbar.describe.steptwo": "Describe", "submit.progressbar.describe.stepcustom": "Describe", - "submit.progressbar.describe.deduplication": "Potential duplicates", "submit.progressbar.describe.recycle": "Recycle", "submit.progressbar.upload": "Upload files", "submit.progressbar.license": "Deposit license", "submit.progressbar.cclicense": "Creative commons license", + "submit.progressbar.detect-duplicate": "Potential duplicates", "upload": { "info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or upload additional files just dragging&dropping them everywhere in the page", @@ -321,20 +321,26 @@ "group-label": "Group" } }, - "deduplication": { - "duplicated": "It's a duplicate", - "not_duplicated": "It's not a duplicate", - "duplicated_ctrl": "Mark the record to merge", - "duplicated_help": "Click here if this is a duplicate of your item", - "not_duplicated_help": "Click here if this is not a duplicate of your item", - "note_help": "Please enter your reason for the duplication into the box below.", - "note_placeholder": "Describe the reason of duplication", - "clear_decision": "Undo", - "clear_decision_help": "Click for clear the decision about this pontential duplicate", - "your_decision": "Your choice:", - "submitter_decision": "Submitter choice:", + "detect-duplicate": { + "duplicate-detected": "Potential duplicate detected", + "duplicate": "It's a duplicate", + "not-duplicate": "It's not a duplicate", + "confirm-duplicate": "Confirm (It's a duplicate)", + "confirm-not-duplicate": "Confirm (It's not a duplicate)", + "duplicate-ctrl": "Mark the record to merge", + "duplicate-help": "Click here if this is a duplicate of your item", + "not-duplicate-help": "Click here if this is not a duplicate of your item", + "note-help": "Please enter your reason for the duplication into the box below.", + "note-placeholder": "Describe the reason of duplication", + "clear-decision": "Undo", + "clear-decision-help": "Click for clear the decision about this pontential duplicate", + "decision-success-notice": "Choice registered successfully", + "no-decision": "No decision yet", + "submitter-decision": "Submitter decision:", + "submitter-note": "Submitter note:", "disclaimer": "The system has identified some potential duplicates. Please carefully review the list and flag each occurency with the appropriate choice or discard this submission.", - "disclaimer_ctrl": "The system has identified some potential duplicates. Please carefully review the list and the submitter comments and perform the appropriate action." + "disclaimer-ctrl": "The system has identified some potential duplicates. Please carefully review the list and the submitter comments and perform the appropriate action.", + "disclaimer-no-match": "All your feedback on potential duplicates have been registered correctly." }, "recycle": { "disclaimer": "The following existent information are not valid within the selected collection. Please copy them to the appropriate metadata if applicable. Use the discard button to remove these information when done." diff --git a/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts b/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts index d74191f87c..9233780be8 100644 --- a/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts @@ -1,14 +1,12 @@ -import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; -import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-upload-file.model'; -import { FormFieldChangedObject } from '../../../shared/form/builder/models/form-field-unexpected-object.model'; +import { Item } from '../../shared/item.model'; -import { DSpaceObject } from '../../shared/dspace-object.model'; - -export interface WorkspaceitemSectionDeduplicationObject { - matches: DeduplicationSchema[]; +export interface WorkspaceitemSectionDetectDuplicateObject { + matches: { + [itemId: string]: DetectDuplicateMatch; + }; } -export interface DeduplicationSchema { +export interface DetectDuplicateMatch { submitterDecision?: string; // [reject|verify] submitterNote?: string; submitterTime?: string; // (readonly) @@ -17,5 +15,7 @@ export interface DeduplicationSchema { workflowNote?: string; workflowTime?: string; // (readonly) - matchObject?: DSpaceObject; // item, workspaceItem, workflowItem + adminDecision?: string; + + matchObject?: Item; } diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index 3de9f9588e..657dc85a4b 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -4,7 +4,7 @@ import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload import { isNotEmpty, isNotNull } from '../../../shared/empty.util'; import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model'; import { WorkspaceitemSectionRecycleObject } from './workspaceitem-section-recycle.model'; -import { WorkspaceitemSectionDeduplicationObject } from './workspaceitem-section-deduplication.model'; +import { WorkspaceitemSectionDetectDuplicateObject } from './workspaceitem-section-deduplication.model'; import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; export class WorkspaceitemSectionsObject { @@ -61,5 +61,5 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionFormObject | WorkspaceitemSectionLicenseObject | WorkspaceitemSectionRecycleObject - | WorkspaceitemSectionDeduplicationObject + | WorkspaceitemSectionDetectDuplicateObject | string; diff --git a/src/app/submission/objects/submission-objects.actions.ts b/src/app/submission/objects/submission-objects.actions.ts index f12ed6d35c..d72d21bf04 100644 --- a/src/app/submission/objects/submission-objects.actions.ts +++ b/src/app/submission/objects/submission-objects.actions.ts @@ -52,12 +52,9 @@ export const SubmissionObjectActionTypes = { DISCARD_SUBMISSION: type('dspace/submission/DISCARD_SUBMISSION'), DISCARD_SUBMISSION_SUCCESS: type('dspace/submission/DISCARD_SUBMISSION_SUCCESS'), DISCARD_SUBMISSION_ERROR: type('dspace/submission/DISCARD_SUBMISSION_ERROR'), - SET_WORKSPACE_DUPLICATION: type('/sections/deduplication/SET_WORKSPACE_DUPLICATION'), - SET_WORKSPACE_DUPLICATION_SUCCESS: type('/sections/deduplication/SET_WORKSPACE_DUPLICATION_SUCCESS'), - SET_WORKSPACE_DUPLICATION_ERROR: type('/sections/deduplication/SET_WORKSPACE_DUPLICATION_ERROR'), - SET_WORKFLOW_DUPLICATION: type('/sections/deduplication/SET_WORKFLOW_DUPLICATION'), - SET_WORKFLOW_DUPLICATION_SUCCESS: type('/sections/deduplication/SET_WORKFLOW_DUPLICATION_SUCCESS'), - SET_WORKFLOW_DUPLICATION_ERROR: type('/sections/deduplication/SET_WORKFLOW_DUPLICATION_ERROR'), + SET_DUPLICATE_DECISION: type('dspace/submission/SET_DUPLICATE_DECISION'), + SET_DUPLICATE_DECISION_SUCCESS: type('dspace/submission/SET_DUPLICATE_DECISION_SUCCESS'), + SET_DUPLICATE_DECISION_ERROR: type('dspace/submission/SET_DUPLICATE_DECISION_ERROR'), // Upload file types NEW_FILE: type('dspace/submission/NEW_FILE'), @@ -763,129 +760,63 @@ export class DeleteUploadedFileAction implements Action { } } -export class SetWorkspaceDuplicatedAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION; +export class SetDuplicateDecisionAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION; payload: { - index: number; - decision: string; - note?: string + submissionId: string; + sectionId: string; }; /** - * Create a new SetWorkspaceDuplicatedAction + * Create a new SetDuplicateDecisionAction * - * @param index - * the index in matches array - * @param decision - * the submitter's decision ('verify'|'reject'|null) - * @param note - * the submitter's note, for 'verify' decision only + * @param submissionId + * the submission's ID + * @param sectionId + * the section's ID */ - constructor(payload: any) { - this.payload = payload; + constructor(submissionId: string, sectionId: string) { + this.payload = { submissionId, sectionId }; } } -export class SetWorkspaceDuplicatedSuccessAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION_SUCCESS; +export class SetDuplicateDecisionSuccessAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS; payload: { - index: number; - decision: string; - note?: string + submissionId: string; + sectionId: string; + submissionObject: SubmissionObject[]; }; /** - * Create a new SetWorkspaceDuplicatedSuccessAction + * Create a new SetDuplicateDecisionSuccessAction * - * @param index - * the index in matches array - * @param decision - * the submitter's decision ('verify'|'reject'|null) - * @param note - * the submitter's note, for 'verify' decision only + * @param submissionId + * the submission's ID + * @param sectionId + * the section's ID + * @param submissionObjects + * the submission's Object */ - constructor(payload: any) { - this.payload = payload; + constructor(submissionId: string, sectionId: string, submissionObject: SubmissionObject[]) { + this.payload = { submissionId, sectionId, submissionObject }; } } -export class SetWorkspaceDuplicatedErrorAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION_ERROR; +export class SetDuplicateDecisionErrorAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_ERROR; payload: { - index: number; + submissionId: string; }; /** - * Create a new SetWorkspaceDuplicatedErrorAction + * Create a new SetDuplicateDecisionErrorAction * - * @param index - * the index in matches array + * @param submissionId + * the submission's ID */ - constructor(index: number) { - this.payload = { index }; - } -} - -export class SetWorkflowDuplicatedAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION; - payload: { - index: number; - decision: string; - note?: string - }; - - /** - * Create a new SetWorkflowDuplicatedAction - * - * @param index - * the index in matches array - * @param decision - * the controller's decision ('verify'|'reject'|null) - * @param note - * the controller's note, for 'verify' decision only - */ - constructor(payload: any) { - this.payload = payload; - } -} - -export class SetWorkflowDuplicatedSuccessAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION_SUCCESS; - payload: { - index: number; - decision: string; - note?: string - }; - - /** - * Create a new SetWorkflowDuplicatedSuccessAction - * - * @param index - * the index in matches array - * @param decision - * the controller's decision ('verify'|'reject'|null) - * @param note - * the controller's note, for 'verify' decision only - */ - constructor(payload: any) { - this.payload = payload; - } -} - -export class SetWorkflowDuplicatedErrorAction implements Action { - type = SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION_ERROR; - payload: { - index: number; - }; - - /** - * Create a new SetWorkflowDuplicatedErrorAction - * - * @param index - * the index in matches array - */ - constructor(index: number) { - this.payload = { index }; + constructor(submissionId: string) { + this.payload = { submissionId }; } } @@ -928,9 +859,6 @@ export type SubmissionObjectAction = DisableSectionAction | SaveSubmissionSectionFormSuccessAction | SaveSubmissionSectionFormErrorAction | SetActiveSectionAction - | SetWorkspaceDuplicatedAction - | SetWorkspaceDuplicatedSuccessAction - | SetWorkspaceDuplicatedErrorAction - | SetWorkflowDuplicatedAction - | SetWorkflowDuplicatedSuccessAction - | SetWorkflowDuplicatedErrorAction; + | SetDuplicateDecisionAction + | SetDuplicateDecisionSuccessAction + | SetDuplicateDecisionErrorAction; diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index a5c520d34e..edfeb514bd 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -22,12 +22,7 @@ import { SaveSubmissionSectionFormAction, SaveSubmissionSectionFormErrorAction, SaveSubmissionSectionFormSuccessAction, - SetWorkflowDuplicatedAction, - SetWorkflowDuplicatedErrorAction, - SetWorkflowDuplicatedSuccessAction, - SetWorkspaceDuplicatedAction, - SetWorkspaceDuplicatedErrorAction, - SetWorkspaceDuplicatedSuccessAction, + SetDuplicateDecisionAction, SetDuplicateDecisionErrorAction, SetDuplicateDecisionSuccessAction, SubmissionObjectActionTypes, UpdateSectionDataAction } from './submission-objects.actions'; @@ -43,11 +38,13 @@ import { Workflowitem } from '../../core/submission/models/workflowitem.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { TranslateService } from '@ngx-translate/core'; -import { DeduplicationService } from '../sections/deduplication/deduplication.service'; +import { DetectDuplicateService } from '../sections/detect-duplicate/detect-duplicate.service'; import { SubmissionState } from '../submission.reducers'; import { SubmissionObjectEntry } from './submission-objects.reducer'; import { SubmissionSectionModel } from '../../core/shared/config/config-submission-section.model'; import parseSectionErrors from '../utils/parseSectionErrors'; +import { SectionsType } from '../sections/sections-type'; +import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; @Injectable() export class SubmissionObjectEffects { @@ -60,7 +57,7 @@ export class SubmissionObjectEffects { definition.sections.forEach((sectionDefinition: SubmissionSectionModel, index: number) => { const sectionId = sectionDefinition._links.self.substr(sectionDefinition._links.self.lastIndexOf('/') + 1); const config = sectionDefinition._links.config || ''; - const enabled = sectionDefinition.mandatory || (isNotEmpty(action.payload.sections) && action.payload.sections.hasOwnProperty(sectionId)); + const enabled = (sectionDefinition.mandatory && sectionDefinition.sectionType !== SectionsType.DetectDuplicate) || (isNotEmpty(action.payload.sections) && action.payload.sections.hasOwnProperty(sectionId)); const sectionData = (isNotUndefined(action.payload.sections) && isNotUndefined(action.payload.sections[sectionId])) ? action.payload.sections[sectionId] : Object.create(null); const sectionErrors = null; mappedActions.push( @@ -127,9 +124,7 @@ export class SubmissionObjectEffects { .map(([action, currentState]: [SaveSubmissionFormSuccessAction | SaveSubmissionSectionFormSuccessAction, any]) => { return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId], action.payload.submissionObject, action.payload.submissionId); }) - .mergeMap((actions) => { - return Observable.from(actions); - }); + .mergeMap((actions) => Observable.from(actions)); @Effect() saveSection$ = this.actions$ .ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM) @@ -143,6 +138,31 @@ export class SubmissionObjectEffects { .catch(() => Observable.of(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId))); }); + @Effect() saveDuplicateDecision$ = this.actions$ + .ofType(SubmissionObjectActionTypes.SET_DUPLICATE_DECISION) + .switchMap((action: SetDuplicateDecisionAction) => { + return this.operationsService.jsonPatchByResourceID( + this.submissionService.getSubmissionObjectLinkName(), + action.payload.submissionId, + 'sections', + action.payload.sectionId) + .map((response: SubmissionObject[]) => new SetDuplicateDecisionSuccessAction(action.payload.submissionId, action.payload.sectionId, response)) + .catch(() => Observable.of(new SetDuplicateDecisionErrorAction(action.payload.submissionId))); + }); + + @Effect({dispatch: false}) setDuplicateDecisionSuccess$ = this.actions$ + .ofType(SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS) + .do(() => this.notificationsService.success(null, this.translate.get('submission.sections.detect-duplicate.decision-success-notice'))); + +/* @Effect() setDuplicateDecisionSuccess$ = this.actions$ + .ofType(SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS) + .withLatestFrom(this.store$) + .map(([action, currentState]: [SetDuplicateDecisionSuccessAction, any]) => { + this.notificationsService.success(null, this.translate.get('submission.sections.detect-duplicate.decision-success-notice')); + return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId], action.payload.submissionObject, action.payload.submissionId, false); + }) + .mergeMap((actions) => Observable.from(actions));*/ + @Effect() saveAndDepositSection$ = this.actions$ .ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION) .withLatestFrom(this.store$) @@ -202,79 +222,13 @@ export class SubmissionObjectEffects { .ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR) .do(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice'))); - @Effect() - public wsDuplication: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION) - .map((action: SetWorkspaceDuplicatedAction) => { - // return this.deduplicationService.setWorkspaceDuplicated(action.payload) - // .first() - // .map((response) => { - console.log('Effect of SET_WORKSPACE_DUPLICATION'); - // TODO JSON PATCH - // const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'deduplication'); - // const path = ''; // `metadata/${metadataKey}`; // TODO - // this.operationsBuilder.add(pathCombiner.getPath(path), action.payload, true); - return new SetWorkspaceDuplicatedSuccessAction(action.payload); - }) - .catch((error) => Observable.of(new SetWorkspaceDuplicatedErrorAction(error))); - - @Effect({dispatch: false}) - public wsDuplicationSuccess: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION_SUCCESS) - // TODO - .do((action: SetWorkspaceDuplicatedAction) => { - console.log('Effect of SET_WORKSPACE_DUPLICATION_SUCCESS'); - this.deduplicationService.setWorkspaceDuplicationSuccess(action.payload); - }); - - @Effect({dispatch: false}) - public wsDuplicationError: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION_ERROR) - .do((action: SetWorkspaceDuplicatedAction) => { - console.log('Effect of SET_WORKSPACE_DUPLICATION_ERROR'); - this.deduplicationService.setWorkspaceDuplicationError(action.payload); - }); - - @Effect() - public wfDuplication: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION) - .map((action: SetWorkflowDuplicatedAction) => { - // return this.deduplicationService.setWorkflowDuplicated(action.payload) - // .first() - // .map((response) => { - console.log('Effect of SET_WORKFLOW_DUPLICATION'); - // TODO JSON PATCH - // const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'deduplication'); - // const path = ''; // `metadata/${metadataKey}`; // TODO - // this.operationsBuilder.add(pathCombiner.getPath(path), action.payload, true); - return new SetWorkflowDuplicatedSuccessAction(action.payload); - }) - .catch((error) => Observable.of(new SetWorkflowDuplicatedErrorAction(error))); - - @Effect({dispatch: false}) - public wfDuplicationSuccess: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION_SUCCESS) - // TODO - .do((action: SetWorkflowDuplicatedAction) => { - console.log('Effect of SET_WORKFLOW_DUPLICATION_SUCCESS'); - this.deduplicationService.setWorkflowDuplicationSuccess(action.payload); - }); - - @Effect({dispatch: false}) - public wfDuplicationError: Observable = this.actions$ - .ofType(SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION_ERROR) - .do((action: SetWorkflowDuplicatedAction) => { - console.log('Effect of SET_WORKFLOW_DUPLICATION_ERROR'); - this.deduplicationService.setWorkflowDuplicationError(action.payload); - }); - constructor(private actions$: Actions, private notificationsService: NotificationsService, private operationsService: JsonPatchOperationsService, private sectionService: SectionsService, private store$: Store, private submissionService: SubmissionService, - private deduplicationService: DeduplicationService, + private deduplicationService: DetectDuplicateService, private translate: TranslateService) { } @@ -293,11 +247,13 @@ export class SubmissionObjectEffects { return canDeposit; } - protected parseSaveResponse(currentState: SubmissionObjectEntry, response: SubmissionObject[], submissionId: string) { + protected parseSaveResponse(currentState: SubmissionObjectEntry, response: SubmissionObject[], submissionId: string, notify: boolean = true) { const mappedActions = []; if (isNotEmpty(response)) { - this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice')); + if (notify) { + this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice')); + } // to avoid dispatching an action for every error, create an array of errors per section response.forEach((item: Workspaceitem | Workflowitem) => { @@ -307,19 +263,20 @@ export class SubmissionObjectEffects { if (errors && !isEmpty(errors)) { errorsList = parseSectionErrors(errors); - this.notificationsService.warning(null, this.translate.get('submission.sections.general.sections_not_valid')); + if (notify) { + this.notificationsService.warning(null, this.translate.get('submission.sections.general.sections_not_valid')); + } } - const sections = (item.sections && isNotEmpty(item.sections)) ? item.sections : {}; - + const sections: WorkspaceitemSectionsObject = (item.sections && isNotEmpty(item.sections)) ? item.sections : {}; const sectionsKeys: string[] = union(Object.keys(sections), Object.keys(errorsList)); sectionsKeys .forEach((sectionId) => { const sectionErrors = errorsList[sectionId] || []; const sectionData = sections[sectionId] || {}; - if (!currentState.sections[sectionId].enabled) { - this.submissionService.notifyNewSection(sectionId); + if (notify && !currentState.sections[sectionId].enabled) { + this.submissionService.notifyNewSection(submissionId, sectionId, currentState.sections[sectionId].sectionType); } mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, sectionErrors)); }); diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index be79df2233..adbd7574b2 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -27,16 +27,18 @@ import { SaveSubmissionFormErrorAction, SaveSubmissionSectionFormSuccessAction, SaveSubmissionSectionFormErrorAction, - SetWorkspaceDuplicatedAction, - SetWorkflowDuplicatedAction, InitSectionAction, RemoveSectionErrorsAction, SaveForLaterSubmissionFormAction, - SaveAndDepositSubmissionAction, SaveForLaterSubmissionFormSuccessAction, SaveForLaterSubmissionFormErrorAction + SaveAndDepositSubmissionAction, + SaveForLaterSubmissionFormSuccessAction, + SaveForLaterSubmissionFormErrorAction, + SetDuplicateDecisionAction, SetDuplicateDecisionSuccessAction, SetDuplicateDecisionErrorAction } from './submission-objects.actions'; import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model'; import { SectionsType } from '../sections/sections-type'; +import { WorkspaceitemSectionDetectDuplicateObject } from '../../core/submission/models/workspaceitem-section-deduplication.model'; export interface SectionVisibility { main: any; @@ -74,6 +76,7 @@ export interface SubmissionObjectEntry { sections?: SubmissionSectionEntry; isLoading?: boolean; savePending?: boolean; + saveDecisionPending?: boolean; depositPending?: boolean; } @@ -192,15 +195,6 @@ export function submissionObjectReducer(state = initialState, action: Submission return deleteFile(state, action as DeleteUploadedFileAction); } - // deduplication - case SubmissionObjectActionTypes.SET_WORKSPACE_DUPLICATION: { - return updateDeduplication(state, action as SetWorkspaceDuplicatedAction); - } - - case SubmissionObjectActionTypes.SET_WORKFLOW_DUPLICATION: { - return updateDeduplication(state, action as SetWorkflowDuplicatedAction); - } - // errors actions case SubmissionObjectActionTypes.ADD_SECTION_ERROR: { return addError(state, action as InertSectionErrorsAction); @@ -214,6 +208,19 @@ export function submissionObjectReducer(state = initialState, action: Submission return removeSectionErrors(state, action as RemoveSectionErrorsAction); } + // detect duplicate + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION: { + return startSaveDecision(state, action as SetDuplicateDecisionAction); + } + + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS: { + return setDuplicateMatches(state, action as SetDuplicateDecisionSuccessAction); + } + + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION: { + return endSaveDecision(state, action as SetDuplicateDecisionErrorAction); + } + default: { return state; } @@ -319,6 +326,7 @@ function initSubmission(state: SubmissionObjectState, action: InitSubmissionForm sections: Object.create(null), isLoading: true, savePending: false, + saveDecisionPending: false, depositPending: false, }; return newState; @@ -740,7 +748,7 @@ function deleteFile(state: SubmissionObjectState, action: DeleteUploadedFileActi [ action.payload.submissionId ]: Object.assign({}, state[action.payload.submissionId], { sections: Object.assign({}, state[action.payload.submissionId].sections, Object.assign({}, { - [ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections [ action.payload.sectionId ], { + [ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections[ action.payload.sectionId ], { data: Object.assign({}, state[ action.payload.submissionId ].sections[ action.payload.sectionId ].data, { files: newData }) @@ -754,25 +762,74 @@ function deleteFile(state: SubmissionObjectState, action: DeleteUploadedFileActi return state; } +// ------ Detect duplicate functions ------ // + /** - * Update a Workspace deduplication match. + * Set decision flag to true * * @param state * the current state * @param action - * a SetWorkspaceDuplicatedAction or SetWorkflowDuplicatedAction + * an SetDuplicateDecisionAction * @return SubmissionObjectState - * the new state, with the match parameter changed. + * the new state, with the decision flag changed. */ -function updateDeduplication(state: SubmissionObjectState, action: SetWorkspaceDuplicatedAction|SetWorkflowDuplicatedAction): SubmissionObjectState { - const matches = Object.assign([], (state[(action.payload as any).submissionId].sections.deduplication.data as any).matches); - const newMatch = (action.payload as any).data; - matches.forEach( (match, i) => { - if (i === action.payload.index) { - matches.splice(i, 1, Object.assign({}, match, newMatch)); - return; - } - }); - // const updatedMatches = Object.assign({}, matches, newMatch); - return Object.assign({}, state, {[(action.payload as any).submissionId]: {sections: {deduplication: {data: {matches}}}}}); +function startSaveDecision(state: SubmissionObjectState, action: SetDuplicateDecisionAction): SubmissionObjectState { + if (hasValue(state[ action.payload.submissionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + saveDecisionPending: true, + }) + }); + } else { + return state; + } +} + +function setDuplicateMatches(state: SubmissionObjectState, action: SetDuplicateDecisionSuccessAction) { + const index: any = findKey( + action.payload.submissionObject, + {id: parseInt(action.payload.submissionId, 10)}); + const sectionData = action.payload.submissionObject[index].sections[ action.payload.sectionId ] as WorkspaceitemSectionDetectDuplicateObject; + const newData = (sectionData && sectionData.matches) ? sectionData : Object.create({}); + + if (hasValue(state[ action.payload.submissionId ].sections[ action.payload.sectionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + sections: Object.assign({}, state[ action.payload.submissionId ].sections, + Object.assign({}, { + [ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections [ action.payload.sectionId ], { + enabled: true, + data: newData + }) + }) + ), + saveDecisionPending: false + }) + }); + } else { + return state; + } +} + +/** + * Set decision flag to false + * + * @param state + * the current state + * @param action + * an SetDuplicateDecisionSuccessAction or SetDuplicateDecisionErrorAction + * @return SubmissionObjectState + * the new state, with the decision flag changed. + */ +function endSaveDecision(state: SubmissionObjectState, action: SetDuplicateDecisionSuccessAction | SetDuplicateDecisionErrorAction): SubmissionObjectState { + if (hasValue(state[ action.payload.submissionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + saveDecisionPending: false, + }) + }); + } else { + return state; + } } diff --git a/src/app/submission/sections/deduplication/deduplication.service.ts b/src/app/submission/sections/deduplication/deduplication.service.ts deleted file mode 100644 index 5b0a029f2a..0000000000 --- a/src/app/submission/sections/deduplication/deduplication.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Store } from '@ngrx/store'; -import { SubmissionState } from '../../submission.reducers'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { HttpHeaders } from '@angular/common/http'; -import { HttpOptions } from '../../../core/dspace-rest-v2/dspace-rest-v2.service'; - -@Injectable() -export class DeduplicationService { - - constructor(private store: Store) { - } - - // setWorkspaceDuplicated(payload: any): Observable { - // const options: HttpOptions = Object.create({}); - // let headers = new HttpHeaders(); - // headers = headers.append('Content-Type', 'application/json'); - // options.headers = headers; - // // TODO REST CALL - // // return this.restService.postToEndpoint('workspace/deduplication', payload, null, options); - // return Observable.of(payload); - // } - - setWorkspaceDuplicationSuccess(payload: any): void { - // TODO - - } - - setWorkspaceDuplicationError(payload: any): void { - // TODO - } - - // setWorkflowDuplicated(payload: any): Observable { - // const options: HttpOptions = Object.create({}); - // let headers = new HttpHeaders(); - // headers = headers.append('Content-Type', 'application/json'); - // options.headers = headers; - // // TODO REST CALL - // // return this.restService.postToEndpoint('workflow/deduplication', payload, null, options); - // return Observable.of(payload); - // } - - setWorkflowDuplicationSuccess(payload: any): void { - // TODO Update the redux store - - } - - setWorkflowDuplicationError(payload: any): void { - // TODO Update the redux store - } -} diff --git a/src/app/submission/sections/deduplication/match/deduplication-match.component.html b/src/app/submission/sections/deduplication/match/deduplication-match.component.html index 2ec4385ddf..28546ed9d3 100644 --- a/src/app/submission/sections/deduplication/match/deduplication-match.component.html +++ b/src/app/submission/sections/deduplication/match/deduplication-match.component.html @@ -1,57 +1,50 @@ - - - - + - - - - - - - - -
- +
+
+
+
+ + {{submitterDecision$ | async}} + +
+
+ + +
+
- - - - - - - -
+
+
-
+
@@ -67,20 +60,19 @@
diff --git a/src/app/submission/sections/deduplication/match/deduplication-match.component.ts b/src/app/submission/sections/deduplication/match/deduplication-match.component.ts index a85239ef5c..bc8e4b7ce0 100644 --- a/src/app/submission/sections/deduplication/match/deduplication-match.component.ts +++ b/src/app/submission/sections/deduplication/match/deduplication-match.component.ts @@ -1,164 +1,146 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; -import { DeduplicationSchema } from '../../../../core/submission/models/workspaceitem-section-deduplication.model'; +import { DetectDuplicateMatch } from '../../../../core/submission/models/workspaceitem-section-deduplication.model'; import { SubmissionService } from '../../../submission.service'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; -import { SubmissionState } from '../../../submission.reducers'; -import { DeduplicationService } from '../deduplication.service'; +import { DetectDuplicateService } from '../detect-duplicate.service'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { TranslateService } from '@ngx-translate/core'; import { Observable } from 'rxjs/Observable'; import { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; +import { DuplicateDecisionValue } from '../models/duplicate-decision-value'; +import { DuplicateDecision } from '../models/duplicate-decision.model'; +import { DuplicateDecisionType } from '../models/duplicate-decision-type'; +import { isNotEmpty } from '../../../../shared/empty.util'; +import { SectionsService } from '../../sections.service'; @Component({ - selector: 'ds-deduplication-match', - templateUrl: 'deduplication-match.component.html', + selector: 'ds-duplicate-match', + templateUrl: 'duplicate-match.component.html', }) -export class DeduplicationMatchComponent implements OnInit { - @Input() - sectionId: string; - @Input() - match: DeduplicationSchema; - @Input() - submissionId: string; - @Input() - index: string; +export class DuplicateMatchComponent implements OnInit { + @Input() sectionId: string; + @Input() itemId: string; + @Input() match: DetectDuplicateMatch; + @Input() submissionId: string; + @Input() index: string; object = {hitHighlights: []}; item: Item; isWorkFlow = false; showSubmitterDecision = false; - submitterDecisionTxt: string; + decisionType: DuplicateDecisionType; + submitterDecision$: Observable; + submitterNote: string; - decidedYet: boolean; + hasDecision: boolean; closeResult: string; // for modal rejectForm: FormGroup; modalRef: NgbModalRef; pathCombiner: JsonPatchOperationPathCombiner; + public processingVerify: Observable = Observable.of(false); + public processingReject: Observable = Observable.of(false); + decisionLabelClass: string; + duplicateBtnLabel$: Observable; + notDuplicateBtnLabel$: Observable; - duplicatedBtnLabel: Observable; - submitterDecisionLabel: Observable; - - constructor(private deduplicationService: DeduplicationService, - private submissionService: SubmissionService, - private modalService: NgbModal, + constructor(private detectDuplicateService: DetectDuplicateService, private formBuilder: FormBuilder, - private store: Store, - protected operationsBuilder: JsonPatchOperationsBuilder, + private modalService: NgbModal, + private operationsBuilder: JsonPatchOperationsBuilder, + private sectionService: SectionsService, + private submissionService: SubmissionService, private translate: TranslateService) { } ngOnInit(): void { - if ((this.match.matchObject as any).item) { - // WSI & WFI - this.item = Object.assign(new Item(), (this.match.matchObject as any).item); - } else { - // Item - this.item = Object.assign(new Item(), this.match.matchObject); - } this.isWorkFlow = this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkflowItem; + this.decisionType = this.isWorkFlow ? DuplicateDecisionType.WORKFLOW : DuplicateDecisionType.WORKSPACE; + this.item = Object.assign(new Item(), this.match.matchObject); this.rejectForm = this.formBuilder.group({ reason: ['', Validators.required] }); - this.decidedYet = this.isWorkFlow ? - this.match.workflowDecision !== null ? true : false - : this.match.submitterDecision !== null ? true : false; + this.hasDecision = this.isWorkFlow ? + this.match.workflowDecision !== null + : this.match.submitterDecision !== null; if (this.match.submitterDecision) { - if (this.match.submitterDecision === 'verify') { - this.submitterDecisionTxt = 'It\'s a duplicate'; - } else { - this.submitterDecisionTxt = 'It\'s not a duplicate'; - } + this.submitterDecision$ = (this.match.submitterDecision === DuplicateDecisionValue.Reject) ? + this.translate.get('submission.sections.detect-duplicate.not-duplicate') : + this.translate.get('submission.sections.detect-duplicate.duplicate'); + this.decisionLabelClass = (this.match.submitterDecision === DuplicateDecisionValue.Reject) ? 'badge-success' : 'badge-warning'; + this.submitterNote = this.match.submitterNote; } else { - this.submitterDecisionTxt = 'Not decided'; + this.submitterDecision$ = this.translate.get('submission.sections.detect-duplicate.no-decision'); + this.decisionLabelClass = 'badge-light'; } - this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'matches', this.index); + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId); - this.duplicatedBtnLabel = this.isWorkFlow ? - this.translate.get('submission.sections.deduplication.duplicated_ctrl') - : this.translate.get('submission.sections.deduplication.duplicated'); + this.duplicateBtnLabel$ = this.isWorkFlow ? + ((this.match.submitterDecision === DuplicateDecisionValue.Verify) ? + this.translate.get('submission.sections.detect-duplicate.confirm-duplicate') : + this.translate.get('submission.sections.detect-duplicate.duplicate-ctrl')) + : this.translate.get('submission.sections.detect-duplicate.duplicate'); - this.submitterDecisionLabel = this.isWorkFlow ? - this.translate.get('submission.sections.deduplication.submitter_decision') - : this.translate.get('submission.sections.deduplication.your_decision'); + this.notDuplicateBtnLabel$ = (this.isWorkFlow && this.match.submitterDecision === DuplicateDecisionValue.Reject) ? + this.translate.get('submission.sections.detect-duplicate.confirm-not-duplicate') : + this.translate.get('submission.sections.detect-duplicate.not-duplicate'); } - setAsDuplicated() { - console.log('Setting item #' + this.item.uuid + ' as duplicated...'); - this.dispatchAction(true); + setAsDuplicate() { + this.processingVerify = Observable.of(true); + const decision = new DuplicateDecision( + DuplicateDecisionValue.Verify, + this.decisionType, + this.rejectForm.get('reason').value); + + this.dispatchAction(decision); this.modalRef.dismiss(); } - setAsNotDuplicated() { - console.log('Setting item #' + this.item.uuid + ' as not duplicated...'); - this.dispatchAction(false); + setAsNotDuplicate() { + this.processingReject = Observable.of(true); + const decision = new DuplicateDecision( + DuplicateDecisionValue.Reject, + this.decisionType); + + this.dispatchAction(decision); } clearDecision() { - console.log('Clearing item #' + this.item.uuid + ' from previous decision...'); + const decision = new DuplicateDecision( + DuplicateDecisionValue.Undo, + this.decisionType); + + this.dispatchAction(decision); } - private dispatchAction(duplicated: boolean, clear?: boolean): void { + private dispatchAction(decision: DuplicateDecision): void { + const pathDecision = Array.of('matches', this.itemId, this.isWorkFlow ? 'workflowDecision' : 'submitterDecision').join('/'); const payload = { - submissionId: this.submissionId, - index: this.index, - data: {} as DeduplicationSchema + value: isNotEmpty(decision.value) ? decision.value : null, + note: isNotEmpty(decision.note) ? decision.note : null }; - // Call workflow action - const decision = clear ? null : duplicated ? 'verify' : 'reject'; - const pathDecision = this.isWorkFlow ? 'workflowDecision' : 'submitterDecision'; - this.operationsBuilder.add(this.pathCombiner.getPath(pathDecision), decision, false, true); + // dispatch patch operation only when section is active + this.sectionService.isSectionActive(this.submissionId, this.sectionId) + .filter((isActive: boolean) => isActive) + .take(1) + .subscribe(() => { + this.operationsBuilder.add(this.pathCombiner.getPath(pathDecision), payload, false, true); + this.detectDuplicateService.saveDuplicateDecision(this.submissionId, this.sectionId) + }); - if (!clear && duplicated) { - const note = this.rejectForm.get('reason').value; - const pathNote = this.isWorkFlow ? 'workflowNote' : 'submitterNote'; - this.operationsBuilder.add(this.pathCombiner.getPath(pathNote), note, false, true); - } - - // const now = new Date(); - // const time = now.getUTCFullYear() + '/' + now.getUTCMonth() + 1 + '/' + now.getDay(); - - // if (this.isWorkFlow) { - // // Call workflow action - // payload.data.workflowDecision = clear ? null : duplicated ? 'verify' : 'reject'; - // // payload.data.workflowTime = time; - // if (!clear && duplicated) { - // const note = this.rejectForm.get('reason').value; - // payload.data.workflowNote = note; - // } - // // Dispatch WorkFLOW action - // // this.store.dispatch(new SetWorkflowDuplicatedAction(payload)); - // const path = 'workflowDecision' - // this.operationsBuilder.add(this.pathCombiner.getPath(path), payload.data.workflowDecision, false, true); - // - // } else { - // // Call workspace action - // payload.data.submitterDecision = clear ? null : duplicated ? 'verify' : 'reject'; - // // payload.data.submitterTime = time; - // if (!clear && duplicated) { - // const note = this.rejectForm.get('reason').value; - // payload.data.submitterNote = note; - // } - // // Dispatch workSPACE action - // this.store.dispatch(new SetWorkspaceDuplicatedAction(payload)); - // } - } - - toggleSubmitterDecision() { - this.showSubmitterDecision = !this.showSubmitterDecision; } openModal(modal) { diff --git a/src/app/submission/sections/deduplication/section-deduplication.component.html b/src/app/submission/sections/deduplication/section-deduplication.component.html index 942d8a75a6..95ed64e5a7 100644 --- a/src/app/submission/sections/deduplication/section-deduplication.component.html +++ b/src/app/submission/sections/deduplication/section-deduplication.component.html @@ -1,38 +1,32 @@ - -
-
-

No duplicated yet.

-
-
+ + - - + +
    -
  • - - + + [index]=totalIndex + [itemId]="item.key">
diff --git a/src/app/submission/sections/deduplication/section-deduplication.component.ts b/src/app/submission/sections/deduplication/section-deduplication.component.ts index 3d08ccc5c3..b8eb118f21 100644 --- a/src/app/submission/sections/deduplication/section-deduplication.component.ts +++ b/src/app/submission/sections/deduplication/section-deduplication.component.ts @@ -1,40 +1,45 @@ import { SectionsType } from '../sections-type'; -import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { SectionModelComponent } from '../models/section.model'; import { renderSectionFor } from '../sections-decorator'; import { SectionDataObject } from '../models/section-data.model'; -import { SubmissionState } from '../../submission.reducers'; -import { Store } from '@ngrx/store'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { submissionSectionDataFromIdSelector } from '../../selectors'; import { Observable } from 'rxjs/Observable'; -import { isNotEmpty } from '../../../shared/empty.util'; import { TranslateService } from '@ngx-translate/core'; import { SubmissionService } from '../../submission.service'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; +import { AlertType } from '../../../shared/alerts/aletrs-type'; +import { DetectDuplicateService } from './detect-duplicate.service'; +import { SectionsService } from '../sections.service'; +import { Subscription } from 'rxjs/Subscription'; +import { hasValue } from '../../../shared/empty.util'; @Component({ selector: 'ds-deduplication-section', // styleUrls: ['./section-deduplication.component.scss'], - templateUrl: './section-deduplication.component.html', + templateUrl: './section-detect-duplicate.component.html', changeDetection: ChangeDetectionStrategy.Default }) -@renderSectionFor(SectionsType.Deduplication) -export class DeduplicationSectionComponent extends SectionModelComponent implements OnInit { +@renderSectionFor(SectionsType.DetectDuplicate) +export class DetectDuplicateSectionComponent extends SectionModelComponent implements OnDestroy, OnInit { + public AlertTypeEnum = AlertType; public isLoading = true; - public sectionDataObs: Observable; - public matches = []; + public sectionData$: Observable; + public matches = {}; + public totalMatch$: Observable; config: PaginationComponentOptions; sortConfig: SortOptions; isWorkFlow = false; disclaimer: Observable; + sub: Subscription; - constructor(protected store: Store, + constructor(private detectDuplicateService: DetectDuplicateService, private translate: TranslateService, + private sectionService: SectionsService, private submissionService: SubmissionService, @Inject('collectionIdProvider') public injectedCollectionId: string, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @@ -48,26 +53,34 @@ export class DeduplicationSectionComponent extends SectionModelComponent impleme this.config.pageSize = 2; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.sectionDataObs = this.store.select(submissionSectionDataFromIdSelector(this.submissionId, this.sectionData.id)) - .filter((sd) => isNotEmpty(sd)) - .startWith({matches: []}) - .distinctUntilChanged() - .map((sd) => { - return sd; - }); + this.sectionData$ = this.detectDuplicateService.getDuplicateMatches(this.submissionId, this.sectionData.id); + + this.totalMatch$ = this.detectDuplicateService.getDuplicateTotalMatches(this.submissionId, this.sectionData.id); this.isWorkFlow = this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkflowItem; this.disclaimer = this.isWorkFlow ? - this.translate.get('submission.sections.deduplication.disclaimer_ctrl') - : this.translate.get('submission.sections.deduplication.disclaimer'); + this.translate.get('submission.sections.detect-duplicate.disclaimer-ctrl') + : this.translate.get('submission.sections.detect-duplicate.disclaimer'); this.isLoading = false; + + this.sub = this.totalMatch$ + .map((totalMatches: number) => totalMatches === 0) + .distinctUntilChanged() + .subscribe((status: boolean) => { + this.sectionService.setSectionStatus(this.submissionId, this.sectionData.id, status); + }) } setPage(page) { - console.log('Select page #', page); this.config.currentPage = page; } + ngOnDestroy(): void { + if (hasValue(this.sub)) { + this.sub.unsubscribe(); + } + } + } diff --git a/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts new file mode 100644 index 0000000000..1f8f3982f8 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts @@ -0,0 +1,33 @@ +import { Store } from '@ngrx/store'; +import { SubmissionState } from '../../submission.reducers'; +import { Injectable } from '@angular/core'; +import { SetDuplicateDecisionAction } from '../../objects/submission-objects.actions'; +import { submissionSectionDataFromIdSelector } from '../../selectors'; +import { WorkspaceitemSectionDetectDuplicateObject } from '../../../core/submission/models/workspaceitem-section-deduplication.model'; +import { isEmpty } from '../../../shared/empty.util'; + +@Injectable() +export class DetectDuplicateService { + + constructor(private store: Store) { + } + + getDuplicateMatches(submissionId: string, sectionId: string) { + return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)) + .map((sectionData: WorkspaceitemSectionDetectDuplicateObject) => { + return (isEmpty(sectionData)) ? {matches: {}} : sectionData + }) + .startWith({matches: {}}) + .distinctUntilChanged(); + } + + getDuplicateTotalMatches(submissionId: string, sectionId: string) { + return this.getDuplicateMatches(submissionId, sectionId) + .map((sectionData: WorkspaceitemSectionDetectDuplicateObject) => Object.keys(sectionData.matches).length) + .distinctUntilChanged(); + } + + saveDuplicateDecision(submissionId: string, sectionId: string): void { + this.store.dispatch(new SetDuplicateDecisionAction(submissionId, sectionId)); + } +} diff --git a/src/app/submission/sections/sections.directive.ts b/src/app/submission/sections/sections.directive.ts index 08d76343d7..0e9f1bb283 100644 --- a/src/app/submission/sections/sections.directive.ts +++ b/src/app/submission/sections/sections.directive.ts @@ -134,7 +134,7 @@ export class SectionsDirective implements OnDestroy, OnInit { public setFocus(event) { if (!this.active) { - this.store.dispatch(new SetActiveSectionAction(this.submissionId, this.sectionId)); + this.submissionService.setActiveSection(this.submissionId, this.sectionId); } } diff --git a/src/app/submission/sections/sections.service.ts b/src/app/submission/sections/sections.service.ts index 04de7346a6..f6a4533526 100644 --- a/src/app/submission/sections/sections.service.ts +++ b/src/app/submission/sections/sections.service.ts @@ -9,7 +9,7 @@ import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empt import { DisableSectionAction, EnableSectionAction, - InertSectionErrorsAction, RemoveSectionErrorsAction, + InertSectionErrorsAction, RemoveSectionErrorsAction, SectionStatusChangeAction, UpdateSectionDataAction } from '../objects/submission-objects.actions'; import { @@ -24,12 +24,14 @@ import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionE import { FormAddError, FormClearErrorsAction, FormRemoveErrorAction } from '../../shared/form/form.actions'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { SubmissionService } from '../submission.service'; @Injectable() export class SectionsService { constructor(private notificationsService: NotificationsService, private scrollToService: ScrollToService, + private submissionService: SubmissionService, private store: Store, private translate: TranslateService) { } @@ -86,6 +88,12 @@ export class SectionsService { .distinctUntilChanged(); } + public isSectionActive(submissionId, sectionId): Observable { + return this.submissionService.getActiveSectionId(submissionId) + .map((activeSectionId: string) => sectionId === activeSectionId) + .distinctUntilChanged(); + } + public isSectionEnabled(submissionId, sectionId): Observable { return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)) .filter((sectionObj) => hasValue(sectionObj)) @@ -150,4 +158,8 @@ export class SectionsService { public setSectionError(submissionId: string, sectionId: string, error: SubmissionSectionError) { this.store.dispatch(new InertSectionErrorsAction(submissionId, sectionId, error)); } + + public setSectionStatus(submissionId: string, sectionId: string, status: boolean) { + this.store.dispatch(new SectionStatusChangeAction(submissionId, sectionId, status)); + } } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index d1a802e0f8..74820276ed 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -29,9 +29,9 @@ import { UploadSectionFileEditComponent } from './sections/upload/file/edit/file import { UploadSectionFileViewComponent } from './sections/upload/file/view/file-view.component'; import { AccessConditionsComponent } from './sections/upload/accessConditions/accessConditions.component'; import { RecycleSectionComponent } from './sections/recycle/section-recycle.component'; -import { DeduplicationSectionComponent } from './sections/deduplication/section-deduplication.component'; -import { DeduplicationMatchComponent } from './sections/deduplication/match/deduplication-match.component'; -import { DeduplicationService } from './sections/deduplication/deduplication.service'; +import { DetectDuplicateSectionComponent } from './sections/detect-duplicate/section-detect-duplicate.component'; +import { DuplicateMatchComponent } from './sections/detect-duplicate/duplicate-match/duplicate-match.component'; +import { DetectDuplicateService } from './sections/detect-duplicate/detect-duplicate.service'; import { SubmissionSubmitComponent } from './submit/submission-submit.component'; @NgModule({ @@ -62,8 +62,8 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' UploadSectionFileEditComponent, UploadSectionFileViewComponent, RecycleSectionComponent, - DeduplicationSectionComponent, - DeduplicationMatchComponent, + DetectDuplicateSectionComponent, + DuplicateMatchComponent, ], entryComponents: [ DefaultSectionComponent, @@ -72,7 +72,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' LicenseSectionComponent, SectionContainerComponent, RecycleSectionComponent, - DeduplicationSectionComponent], + DetectDuplicateSectionComponent], exports: [ SubmissionEditComponent, SubmissionFormComponent, @@ -83,7 +83,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' SectionsService, SubmissionRestService, SubmissionUploadsConfigService, - DeduplicationService + DetectDuplicateService ] }) export class SubmissionModule { diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index f7f532aa16..adb11afe8f 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -7,7 +7,7 @@ import { Store } from '@ngrx/store'; import { submissionSelector, SubmissionState } from './submission.reducers'; import { hasValue, isEmpty, isNotUndefined } from '../shared/empty.util'; -import { SaveSubmissionFormAction } from './objects/submission-objects.actions'; +import { SaveSubmissionFormAction, SetActiveSectionAction } from './objects/submission-objects.actions'; import { SubmissionObjectEntry, SubmissionSectionEntry, @@ -26,6 +26,8 @@ import { RouteService } from '../shared/services/route.service'; import { SectionsType } from './sections/sections-type'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../shared/notifications/notifications.service'; +import { ScrollToConfigOptions, ScrollToService } from '@nicky-lenaers/ngx-scroll-to'; +import { NotificationOptions } from '../shared/notifications/models/notification-options.model'; @Injectable() export class SubmissionService { @@ -38,6 +40,7 @@ export class SubmissionService { protected restService: SubmissionRestService, protected router: Router, protected routeService: RouteService, + protected scrollToService: ScrollToService, protected store: Store, protected translate: TranslateService) { } @@ -79,6 +82,7 @@ export class SubmissionService { const availableSections: SectionDataObject[] = []; Object.keys(sections) .filter((sectionId) => !this.isSectionHidden(sections[sectionId] as SubmissionSectionObject)) + // .filter((sectionId) => sections[sectionId].sectionType !== SectionsType.DetectDuplicate || isNotEmpty(sections[sectionId].data)) .forEach((sectionId) => { const sectionObject: SectionDataObject = Object.create({}); sectionObject.config = sections[sectionId].config; @@ -105,6 +109,7 @@ export class SubmissionService { Object.keys(sections) .filter((sectionId) => !this.isSectionHidden(sections[sectionId] as SubmissionSectionObject)) .filter((sectionId) => !sections[sectionId].enabled) + .filter((sectionId) => sections[sectionId].sectionType !== SectionsType.DetectDuplicate) .forEach((sectionId) => { const sectionObject: SectionDataObject = Object.create({}); sectionObject.header = sections[sectionId].header; @@ -196,6 +201,13 @@ export class SubmissionService { .startWith(false); } + getSubmissionDuplicateDecisionProcessingStatus(submissionId: string): Observable { + return this.getSubmissionObject(submissionId) + .map((state: SubmissionObjectEntry) => state.saveDecisionPending) + .distinctUntilChanged() + .startWith(false); + } + redirectToMyDSpace() { const previousUrl = this.routeService.getPreviousUrl(); if (isEmpty(previousUrl)) { @@ -205,12 +217,28 @@ export class SubmissionService { } } - notifyNewSection(sectionId: string, sectionType?: SectionsType) { - this.translate.get('submission.sections.general.metadata-extracted-new-section', {sectionId}) - .take(1) - .subscribe((m) => { - this.notificationsService.info(null, m, null, true); - }); + notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) { + + if (sectionType === SectionsType.DetectDuplicate) { + this.setActiveSection(submissionId, sectionId); + this.translate.get('submission.sections.detect-duplicate.duplicate-detected', {sectionId}) + .take(1) + .subscribe((msg) => { + this.notificationsService.warning(null, msg, new NotificationOptions(0)); + }); + const config: ScrollToConfigOptions = { + target: sectionId, + offset: -70 + }; + + this.scrollToService.scrollTo(config); + } else { + this.translate.get('submission.sections.general.metadata-extracted-new-section', {sectionId}) + .take(1) + .subscribe((msg) => { + this.notificationsService.info(null, msg, null, true); + }); + } } retrieveSubmission(submissionId): Observable { return this.restService.getDataById(this.getSubmissionObjectLinkName(), submissionId) @@ -219,6 +247,10 @@ export class SubmissionService { .map((submissionObjects: SubmissionObject[]) => submissionObjects[0]); } + setActiveSection(submissionId, sectionId) { + this.store.dispatch(new SetActiveSectionAction(submissionId, sectionId)); + } + startAutoSave(submissionId) { this.stopAutoSave(); console.log('AUTOSAVE ON!!!');