Final commit detect duplicate

This commit is contained in:
Giuseppe
2018-10-03 10:33:54 +02:00
parent 25bb5fddf6
commit 6f4b9fc818
16 changed files with 461 additions and 506 deletions

View File

@@ -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 <strong>upload additional files just dragging&dropping them everywhere in the page</strong>",
@@ -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."

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Action> = 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<Action> = 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<Action> = 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<Action> = 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<Action> = 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<Action> = 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<SubmitDataResponseDefinitionObject>,
private sectionService: SectionsService,
private store$: Store<any>,
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));
});

View File

@@ -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;
}
}

View File

@@ -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<SubmissionState>) {
}
// setWorkspaceDuplicated(payload: any): Observable<any> {
// 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<any> {
// 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
}
}

View File

@@ -1,57 +1,50 @@
<!--<ds-item-list-preview-->
<!--[item]="item"-->
<!--[object]="object"-->
<!--&gt;</ds-item-list-preview>-->
<ds-item-list-preview
[item]="item"
[object]="object"
></ds-item-list-preview>
<!--<button (click)="toggleSubmitterDecision()"-->
<!--class="btn btn-link">-->
<!--<span *ngIf="isWorkFlow && !showSubmitterDecision"> <i class="fa fa-angle-double-down"></i> Show submitter decision</span>-->
<!--<span *ngIf="isWorkFlow && showSubmitterDecision"> <i class="fa fa-angle-double-up"></i> Hide submitter decision</span>-->
<!--</button>-->
<!--<div *ngIf="showSubmitterDecision">-->
<div>
<label><strong> {{submitterDecisionLabel | async}} </strong>
<span [ngClass]="{'label': true,
'label-warning': match.submitterDecision == 'verify',
'label-success': match.submitterDecision == 'reject',
'label-default': (match.submitterDecision == undefined || match.submitterDecision == null)}">
{{submitterDecisionTxt}}
</span>
</label>
<div *ngIf="isWorkFlow" class="mt-2">
<form>
<div class="form-group mb-2">
<label class="mb-1" for="submitterDecision"><strong> {{'submission.sections.detect-duplicate.submitter-decision' | translate}} </strong></label><br>
<span id="submitterDecision" class="badge badge-pill {{decisionLabelClass}}">
{{submitterDecision$ | async}}
</span>
</div>
<div class="form-group" *ngIf="submitterNote">
<label for="submitterNote"><strong>{{'submission.sections.detect-duplicate.submitter-note' | translate}}</strong></label>
<textarea class="form-control" id="submitterNote" rows="3" readonly>{{submitterNote}}</textarea>
</div>
</form>
</div>
<!--<div>-->
<!--<label><strong> Explaination: </strong>-->
<!--<span class="label">-->
<!--I want to provide the fulltext of this article-->
<!--</span>-->
<!--</label>-->
<!--</div>-->
<div class="mb-3" *ngIf="!decidedYet">
<div class="mt-3 mb-2" *ngIf="!hasDecision">
<button type="button"
class="btn btn-warning"
ngbTooltip="{{'submission.sections.deduplication.duplicated_help' | translate}}"
ngbTooltip="{{'submission.sections.detect-duplicate.duplicate-help' | translate}}"
[disabled]="(processingVerify | async) || (processingReject | async)"
(click)="openModal(modal)">
<span> {{duplicatedBtnLabel | async}}</span>
<span *ngIf="(processingVerify | async)"><i class='fa fa-circle-o-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processingVerify | async)">{{duplicateBtnLabel$ | async}}</span>
</button>
<button type="button"
class="btn btn-success"
ngbTooltip="{{'submission.sections.deduplication.not_duplicated_help' | translate}}"
(click)="setAsNotDuplicated()">
<span> {{'submission.sections.deduplication.not_duplicated' | translate}}</span>
ngbTooltip="{{'submission.sections.detect-duplicate.not-duplicate-help' | translate}}"
[disabled]="(processingReject | async) || (processingVerify | async)"
(click)="setAsNotDuplicate()">
<span *ngIf="(processingReject | async)"><i class='fa fa-circle-o-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processingReject | async)">{{notDuplicateBtnLabel$ | async}}</span>
</button>
</div>
<div class="mb-3" *ngIf="decidedYet">
<div class="mt-3 mb-2" *ngIf="hasDecision">
<button type="button"
class="btn btn-danger"
ngbTooltip="{{'submission.sections.deduplication.clear_decision_help' | translate}}"
ngbTooltip="{{'submission.sections.detect-duplicate.clear-decision-help' | translate}}"
(click)="clearDecision()">
<span> {{'submission.sections.deduplication.clear_decision' | translate}}</span>
<span> {{'submission.sections.detect-duplicate.clear-decision' | translate}}</span>
</button>
</div>
@@ -67,20 +60,19 @@
</div>
<div class="modal-body">
<div class="alert alert-info" role="alert">
{{'submission.sections.deduplication.note_help' | translate}}
{{'submission.sections.detect-duplicate.note-help' | translate}}
</div>
<form (ngSubmit)="setAsDuplicated();" [formGroup]="rejectForm" >
<textarea
style="width: 100%"
formControlName="reason"
rows="4"
placeholder="{{'submission.sections.deduplication.note_placeholder' | translate}}"></textarea>
<button
id="btn-chat"
class="btn btn-danger btn-lg btn-block mt-3"
[disabled]="!rejectForm.valid"
type="submit">
<span>{{'submission.sections.deduplication.duplicated' | translate}}</span>
<form (ngSubmit)="setAsDuplicate();" [formGroup]="rejectForm" >
<textarea class="w-100"
formControlName="reason"
rows="4"
placeholder="{{'submission.sections.detect-duplicate.note-placeholder' | translate}}"></textarea>
<button id="btn-chat"
class="btn btn-danger btn-lg btn-block mt-3"
[disabled]="!rejectForm.valid"
type="submit">
<span *ngIf="(processingReject | async)"><i class='fa fa-circle-o-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processingReject | async)">{{'submission.sections.detect-duplicate.duplicate' | translate}}</span>
</button>
</form>
</div>

View File

@@ -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<string>;
submitterNote: string;
decidedYet: boolean;
hasDecision: boolean;
closeResult: string; // for modal
rejectForm: FormGroup;
modalRef: NgbModalRef;
pathCombiner: JsonPatchOperationPathCombiner;
public processingVerify: Observable<boolean> = Observable.of(false);
public processingReject: Observable<boolean> = Observable.of(false);
decisionLabelClass: string;
duplicateBtnLabel$: Observable<string>;
notDuplicateBtnLabel$: Observable<string>;
duplicatedBtnLabel: Observable<string>;
submitterDecisionLabel: Observable<string>;
constructor(private deduplicationService: DeduplicationService,
private submissionService: SubmissionService,
private modalService: NgbModal,
constructor(private detectDuplicateService: DetectDuplicateService,
private formBuilder: FormBuilder,
private store: Store<SubmissionState>,
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) {

View File

@@ -1,38 +1,32 @@
<ds-loading *ngIf="isLoading" message="Loading..."></ds-loading>
<ng-container *ngIf="(sectionDataObs | async)?.matches.length == 0">
<div class="row">
<div class="col-md-12">
<h3 class="text-center"><span class="text-muted">No duplicated yet.</span></h3>
</div>
</div>
<ng-container *ngIf="(totalMatch$ | async) === 0">
<ds-alert [type]="AlertTypeEnum.Info" [content]="('submission.sections.detect-duplicate.disclaimer-no-match' | translate)"></ds-alert>
</ng-container>
<ng-container
*ngIf="(sectionDataObs | async)?.matches.length > 0">
<div class="alert alert-danger" role="alert">
{{ disclaimer | async }}
</div>
<ng-container *ngIf="(totalMatch$ | async) > 0">
<ds-alert [type]="AlertTypeEnum.Warning" [content]="(disclaimer | async)"></ds-alert>
<ds-pagination
[paginationOptions]="config"
[collectionSize]="(sectionDataObs | async)?.matches.length"
[collectionSize]="(totalMatch$ | async)"
[sortOptions]="sortConfig"
[hideGear]="true"
[hidePagerWhenSinglePage]="false"
(pageChange)="setPage($event)">
<ul class="list-unstyled">
<li *ngFor="let match of (sectionDataObs | async)?.matches
| paginate: {id: config.id, itemsPerPage: config.pageSize, currentPage: config.currentPage}; let i = index"
class="mt-4 mb-4">
<ds-deduplication-match
<li *ngFor="let item of (sectionData$ | async)?.matches | dsObjNgFor
| paginate: {id: config.id, itemsPerPage: config.pageSize, currentPage: config.currentPage}; let i = index; let l = last"
class="mt-4 mb-4"
[class.border-bottom]="!l">
<ds-duplicate-match
*ngVar="(i + (config.currentPage-1)*config.pageSize) as totalIndex"
[sectionId]="sectionData.id"
[match]="match"
[match]="item.value"
[submissionId]="submissionId"
[index]=totalIndex></ds-deduplication-match>
[index]=totalIndex
[itemId]="item.key"></ds-duplicate-match>
</li>
</ul>
</ds-pagination>

View File

@@ -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<any>;
public matches = [];
public sectionData$: Observable<any>;
public matches = {};
public totalMatch$: Observable<number>;
config: PaginationComponentOptions;
sortConfig: SortOptions;
isWorkFlow = false;
disclaimer: Observable<string>;
sub: Subscription;
constructor(protected store: Store<SubmissionState>,
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();
}
}
}

View File

@@ -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<SubmissionState>) {
}
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));
}
}

View File

@@ -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);
}
}

View File

@@ -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<SubmissionState>,
private translate: TranslateService) {
}
@@ -86,6 +88,12 @@ export class SectionsService {
.distinctUntilChanged();
}
public isSectionActive(submissionId, sectionId): Observable<boolean> {
return this.submissionService.getActiveSectionId(submissionId)
.map((activeSectionId: string) => sectionId === activeSectionId)
.distinctUntilChanged();
}
public isSectionEnabled(submissionId, sectionId): Observable<boolean> {
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));
}
}

View File

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

View File

@@ -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<SubmissionState>,
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<boolean> {
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<SubmissionObject> {
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!!!');