diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index b575aecd72..fb88448f7b 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -217,6 +217,7 @@ import { AuthorizedCollectionSelectorComponent } from './dso-selector/dso-select
import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-page-edit-button.component';
import { HoverClassDirective } from './hover-class.directive';
import { ValidationSuggestionsComponent } from './input-suggestions/validation-suggestions/validation-suggestions.component';
+import { ItemAlertsComponent } from './item/item-alerts/item-alerts.component';
import { ItemSearchResultGridElementComponent } from './object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component';
import { ResourcePolicyEditComponent } from './resource-policies/edit/resource-policy-edit.component';
import { ResourcePolicyCreateComponent } from './resource-policies/create/resource-policy-create.component';
@@ -522,7 +523,8 @@ const ENTRY_COMPONENTS = [
const SHARED_ITEM_PAGE_COMPONENTS = [
MetadataFieldWrapperComponent,
MetadataValuesComponent,
- DsoPageEditButtonComponent
+ DsoPageEditButtonComponent,
+ ItemAlertsComponent,
];
const PROVIDERS = [
diff --git a/src/app/shared/testing/sections-service.stub.ts b/src/app/shared/testing/sections-service.stub.ts
index 2110d71d8e..3b311c5e19 100644
--- a/src/app/shared/testing/sections-service.stub.ts
+++ b/src/app/shared/testing/sections-service.stub.ts
@@ -2,6 +2,7 @@ export class SectionsServiceStub {
checkSectionErrors = jasmine.createSpy('checkSectionErrors');
dispatchRemoveSectionErrors = jasmine.createSpy('dispatchRemoveSectionErrors');
+ dispatchSetSectionFormId = jasmine.createSpy('dispatchSetSectionFormId');
getSectionData = jasmine.createSpy('getSectionData');
getSectionErrors = jasmine.createSpy('getSectionErrors');
getSectionState = jasmine.createSpy('getSectionState');
@@ -14,5 +15,5 @@ export class SectionsServiceStub {
updateSectionData = jasmine.createSpy('updateSectionData');
setSectionError = jasmine.createSpy('setSectionError');
setSectionStatus = jasmine.createSpy('setSectionStatus');
-
+ computeSectionConfiguredMetadata = jasmine.createSpy('computeSectionConfiguredMetadata');
}
diff --git a/src/app/shared/testing/submission-service.stub.ts b/src/app/shared/testing/submission-service.stub.ts
index 35c3ddfee0..d9d28bde0e 100644
--- a/src/app/shared/testing/submission-service.stub.ts
+++ b/src/app/shared/testing/submission-service.stub.ts
@@ -20,6 +20,7 @@ export class SubmissionServiceStub {
getSubmissionStatus = jasmine.createSpy('getSubmissionStatus');
getSubmissionSaveProcessingStatus = jasmine.createSpy('getSubmissionSaveProcessingStatus');
getSubmissionDepositProcessingStatus = jasmine.createSpy('getSubmissionDepositProcessingStatus');
+ hasUnsavedModification = jasmine.createSpy('hasUnsavedModification');
isSectionHidden = jasmine.createSpy('isSectionHidden');
isSubmissionLoading = jasmine.createSpy('isSubmissionLoading');
notifyNewSection = jasmine.createSpy('notifyNewSection');
diff --git a/src/app/submission/form/footer/submission-form-footer.component.html b/src/app/submission/form/footer/submission-form-footer.component.html
index 938c81a33f..459241ae1c 100644
--- a/src/app/submission/form/footer/submission-form-footer.component.html
+++ b/src/app/submission/form/footer/submission-form-footer.component.html
@@ -12,7 +12,7 @@
diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts
index d9d58aa4f2..704346f445 100644
--- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts
+++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts
@@ -169,7 +169,7 @@ describe('SubmissionFormFooterComponent Component', () => {
comp.save(null);
fixture.detectChanges();
- expect(submissionServiceStub.dispatchSave).toHaveBeenCalledWith(submissionId);
+ expect(submissionServiceStub.dispatchSave).toHaveBeenCalledWith(submissionId, true);
});
it('should call dispatchSaveForLater on save for later', () => {
@@ -224,6 +224,22 @@ describe('SubmissionFormFooterComponent Component', () => {
expect(depositBtn.nativeElement.disabled).toBeFalsy();
});
+ it('should disable save button when all modifications had been saved', () => {
+ comp.hasUnsavedModification = observableOf(false);
+ fixture.detectChanges();
+
+ const saveBtn: any = fixture.debugElement.query(By.css('#save'));
+ expect(saveBtn.nativeElement.disabled).toBeTruthy();
+ });
+
+ it('should enable save button when there are not saved modifications', () => {
+ comp.hasUnsavedModification = observableOf(true);
+ fixture.detectChanges();
+
+ const saveBtn: any = fixture.debugElement.query(By.css('#save'));
+ expect(saveBtn.nativeElement.disabled).toBeFalsy();
+ });
+
});
});
diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts
index 1b885b98b8..9cabdcbf6d 100644
--- a/src/app/submission/form/footer/submission-form-footer.component.ts
+++ b/src/app/submission/form/footer/submission-form-footer.component.ts
@@ -49,6 +49,11 @@ export class SubmissionFormFooterComponent implements OnChanges {
*/
public submissionIsInvalid: Observable
= observableOf(true);
+ /**
+ * A boolean representing if submission form has unsaved modifications
+ */
+ public hasUnsavedModification: Observable;
+
/**
* Initialize instance variables
*
@@ -73,6 +78,7 @@ export class SubmissionFormFooterComponent implements OnChanges {
this.processingSaveStatus = this.submissionService.getSubmissionSaveProcessingStatus(this.submissionId);
this.processingDepositStatus = this.submissionService.getSubmissionDepositProcessingStatus(this.submissionId);
this.showDepositAndDiscard = observableOf(this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkspaceItem);
+ this.hasUnsavedModification = this.submissionService.hasUnsavedModification();
}
}
@@ -80,7 +86,7 @@ export class SubmissionFormFooterComponent implements OnChanges {
* Dispatch a submission save action
*/
save(event) {
- this.submissionService.dispatchSave(this.submissionId);
+ this.submissionService.dispatchSave(this.submissionId, true);
}
/**
diff --git a/src/app/submission/objects/submission-objects.actions.ts b/src/app/submission/objects/submission-objects.actions.ts
index 73c070846c..962f216c7d 100644
--- a/src/app/submission/objects/submission-objects.actions.ts
+++ b/src/app/submission/objects/submission-objects.actions.ts
@@ -40,6 +40,7 @@ export const SubmissionObjectActionTypes = {
INIT_SECTION: type('dspace/submission/INIT_SECTION'),
ENABLE_SECTION: type('dspace/submission/ENABLE_SECTION'),
DISABLE_SECTION: type('dspace/submission/DISABLE_SECTION'),
+ SET_SECTION_FORM_ID: type('dspace/submission/SET_SECTION_FORM_ID'),
SECTION_STATUS_CHANGE: type('dspace/submission/SECTION_STATUS_CHANGE'),
SECTION_LOADING_STATUS_CHANGE: type('dspace/submission/SECTION_LOADING_STATUS_CHANGE'),
UPDATE_SECTION_DATA: type('dspace/submission/UPDATE_SECTION_DATA'),
@@ -206,6 +207,7 @@ export class UpdateSectionDataAction implements Action {
sectionId: string;
data: WorkspaceitemSectionDataType;
errors: SubmissionSectionError[];
+ metadata: string[];
};
/**
@@ -219,12 +221,15 @@ export class UpdateSectionDataAction implements Action {
* the section's data
* @param errors
* the section's errors
+ * @param metadata
+ * the section's metadata
*/
constructor(submissionId: string,
sectionId: string,
data: WorkspaceitemSectionDataType,
- errors: SubmissionSectionError[]) {
- this.payload = { submissionId, sectionId, data, errors };
+ errors: SubmissionSectionError[],
+ metadata?: string[]) {
+ this.payload = { submissionId, sectionId, data, errors, metadata };
}
}
@@ -252,6 +257,29 @@ export class RemoveSectionErrorsAction implements Action {
}
}
+export class SetSectionFormId implements Action {
+ type = SubmissionObjectActionTypes.SET_SECTION_FORM_ID;
+ payload: {
+ submissionId: string;
+ sectionId: string;
+ formId: string;
+ };
+
+ /**
+ * Create a new SetSectionFormId
+ *
+ * @param submissionId
+ * the submission's ID
+ * @param sectionId
+ * the section's ID
+ * @param formId
+ * the section's formId
+ */
+ constructor(submissionId: string, sectionId: string, formId: string) {
+ this.payload = { submissionId, sectionId, formId };
+ }
+}
+
// Submission actions
export class CompleteInitSubmissionFormAction implements Action {
@@ -368,6 +396,7 @@ export class SaveSubmissionFormAction implements Action {
type = SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM;
payload: {
submissionId: string;
+ isManual?: boolean;
};
/**
@@ -376,8 +405,8 @@ export class SaveSubmissionFormAction implements Action {
* @param submissionId
* the submission's ID
*/
- constructor(submissionId: string) {
- this.payload = { submissionId };
+ constructor(submissionId: string, isManual: boolean = false) {
+ this.payload = { submissionId, isManual };
}
}
@@ -777,6 +806,7 @@ export class DeleteUploadedFileAction implements Action {
*/
export type SubmissionObjectAction = DisableSectionAction
| InitSectionAction
+ | SetSectionFormId
| EnableSectionAction
| InitSubmissionFormAction
| ResetSubmissionFormAction
diff --git a/src/app/submission/objects/submission-objects.effects.spec.ts b/src/app/submission/objects/submission-objects.effects.spec.ts
index c35968c0a0..477a84531e 100644
--- a/src/app/submission/objects/submission-objects.effects.spec.ts
+++ b/src/app/submission/objects/submission-objects.effects.spec.ts
@@ -32,7 +32,7 @@ import {
mockSubmissionId,
mockSubmissionSelfUrl,
mockSubmissionState,
- mockSubmissionRestResponse
+ mockSubmissionRestResponse, mockSectionsErrorsTwo
} from '../../shared/mocks/submission.mock';
import { SubmissionSectionModel } from '../../core/config/models/config-submission-section.model';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
@@ -51,15 +51,16 @@ import { Item } from '../../core/shared/item.model';
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../core/submission/workflowitem-data.service';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
+import {formStateSelector} from '../../shared/form/selectors';
describe('SubmissionObjectEffects test suite', () => {
let submissionObjectEffects: SubmissionObjectEffects;
let actions: Observable;
let store: StoreMock;
- const notificationsServiceStub = new NotificationsServiceStub();
- const submissionServiceStub = new SubmissionServiceStub();
- const submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub();
+ let notificationsServiceStub;
+ let submissionServiceStub;
+ let submissionJsonPatchOperationsServiceStub;
const collectionId: string = mockSubmissionCollectionId;
const submissionId: string = mockSubmissionId;
const submissionDefinitionResponse: any = mockSubmissionDefinitionResponse;
@@ -68,6 +69,11 @@ describe('SubmissionObjectEffects test suite', () => {
const submissionState: any = Object.assign({}, mockSubmissionState);
beforeEach(() => {
+
+ notificationsServiceStub = new NotificationsServiceStub();
+ submissionServiceStub = new SubmissionServiceStub();
+ submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub();
+
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}, storeModuleConfig),
@@ -206,6 +212,52 @@ describe('SubmissionObjectEffects test suite', () => {
expect(submissionObjectEffects.saveSubmission$).toBeObservable(expected);
});
+ it('should enable notifications if is manual', () => {
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM,
+ payload: {
+ submissionId: submissionId,
+ isManual: true
+ }
+ }
+ });
+
+ submissionJsonPatchOperationsServiceStub.jsonPatchByResourceType.and.returnValue(observableOf(mockSubmissionRestResponse));
+ const expected = cold('--b-', {
+ b: new SaveSubmissionFormSuccessAction(
+ submissionId,
+ mockSubmissionRestResponse as any,
+ true
+ )
+ });
+
+ expect(submissionObjectEffects.saveSubmission$).toBeObservable(expected);
+ });
+
+ it('should disable notifications if is not manual', () => {
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM,
+ payload: {
+ submissionId: submissionId,
+ isManual: false
+ }
+ }
+ });
+
+ submissionJsonPatchOperationsServiceStub.jsonPatchByResourceType.and.returnValue(observableOf(mockSubmissionRestResponse));
+ const expected = cold('--b-', {
+ b: new SaveSubmissionFormSuccessAction(
+ submissionId,
+ mockSubmissionRestResponse as any,
+ false
+ )
+ });
+
+ expect(submissionObjectEffects.saveSubmission$).toBeObservable(expected);
+ });
+
it('should return a SAVE_SUBMISSION_FORM_ERROR action on error', () => {
actions = hot('--a-', {
a: {
@@ -292,7 +344,8 @@ describe('SubmissionObjectEffects test suite', () => {
type: SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS,
payload: {
submissionId: submissionId,
- submissionObject: response
+ submissionObject: response,
+ notify: true
}
}
});
@@ -324,6 +377,61 @@ describe('SubmissionObjectEffects test suite', () => {
});
+ it('should not display errors when notification are disabled and field are not touched', () => {
+ store.nextState({
+ submission: {
+ objects: submissionState
+ },
+ forms: {
+ '2_traditionalpageone': {
+ touched: {
+ 'dc.title': true
+ }
+ }
+ }
+ } as any);
+
+ const response = [Object.assign({}, mockSubmissionRestResponse[0], {
+ sections: mockSectionsData,
+ errors: mockSectionsErrors
+ })];
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
+ payload: {
+ submissionId: submissionId,
+ submissionObject: response,
+ notify: false
+ }
+ }
+ });
+
+ const errorsList = parseSectionErrors(mockSectionsErrorsTwo);
+ const expected = cold('--(bcd)-', {
+ b: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpageone',
+ mockSectionsData.traditionalpageone as any,
+ errorsList.traditionalpageone
+ ),
+ c: new UpdateSectionDataAction(
+ submissionId,
+ 'license',
+ mockSectionsData.license as any,
+ errorsList.license || []
+ ),
+ d: new UpdateSectionDataAction(
+ submissionId,
+ 'upload',
+ mockSectionsData.upload as any,
+ errorsList.upload || []
+ ),
+ });
+
+ expect(submissionObjectEffects.saveSubmissionSectionSuccess$).toBeObservable(expected);
+ expect(notificationsServiceStub.warning).not.toHaveBeenCalled();
+ });
+
it('should display a success notification', () => {
store.nextState({
submission: {
@@ -471,6 +579,203 @@ describe('SubmissionObjectEffects test suite', () => {
});
+ describe('saveSubmissionSectionSuccess$', () => {
+
+ it('should return a UPDATE_SECTION_DATA action for each updated section', () => {
+ store.nextState({
+ submission: {
+ objects: submissionState
+ }
+ } as any);
+
+ const response = [Object.assign({}, mockSubmissionRestResponse[0], {
+ sections: mockSectionsData,
+ errors: mockSectionsErrors
+ })];
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
+ payload: {
+ submissionId: submissionId,
+ submissionObject: response
+ }
+ }
+ });
+
+ const errorsList = parseSectionErrors(mockSectionsErrors);
+ const expected = cold('--(bcd)-', {
+ b: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpageone',
+ mockSectionsData.traditionalpageone as any,
+ []
+ ),
+ c: new UpdateSectionDataAction(
+ submissionId,
+ 'license',
+ mockSectionsData.license as any,
+ errorsList.license || []
+ ),
+ d: new UpdateSectionDataAction(
+ submissionId,
+ 'upload',
+ mockSectionsData.upload as any,
+ errorsList.upload || []
+ ),
+ });
+
+ expect(submissionObjectEffects.saveSubmissionSectionSuccess$).toBeObservable(expected);
+
+ });
+
+ it('should not display a success notification', () => {
+ store.nextState({
+ submission: {
+ objects: submissionState
+ }
+ } as any);
+
+ const response = [Object.assign({}, mockSubmissionRestResponse[0], {
+ sections: mockSectionsData
+ })];
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
+ payload: {
+ submissionId: submissionId,
+ submissionObject: response
+ }
+ }
+ });
+
+ const expected = cold('--(bcd)-', {
+ b: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpageone',
+ mockSectionsData.traditionalpageone as any,
+ []
+ ),
+ c: new UpdateSectionDataAction(
+ submissionId,
+ 'license',
+ mockSectionsData.license as any,
+ []
+ ),
+ d: new UpdateSectionDataAction(
+ submissionId,
+ 'upload',
+ mockSectionsData.upload as any,
+ []
+ ),
+ });
+
+ expect(submissionObjectEffects.saveSubmissionSectionSuccess$).toBeObservable(expected);
+ expect(notificationsServiceStub.success).not.toHaveBeenCalled();
+ });
+
+ it('should not display a warning notification when there are errors', () => {
+ store.nextState({
+ submission: {
+ objects: submissionState
+ }
+ } as any);
+
+ const response = [Object.assign({}, mockSubmissionRestResponse[0], {
+ sections: mockSectionsData,
+ errors: mockSectionsErrors
+ })];
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
+ payload: {
+ submissionId: submissionId,
+ submissionObject: response
+ }
+ }
+ });
+
+ const errorsList = parseSectionErrors(mockSectionsErrors);
+ console.log(errorsList);
+ const expected = cold('--(bcd)-', {
+ b: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpageone',
+ mockSectionsData.traditionalpageone as any,
+ []
+ ),
+ c: new UpdateSectionDataAction(
+ submissionId,
+ 'license',
+ mockSectionsData.license as any,
+ errorsList.license || []
+ ),
+ d: new UpdateSectionDataAction(
+ submissionId,
+ 'upload',
+ mockSectionsData.upload as any,
+ errorsList.upload || []
+ ),
+ });
+
+ expect(submissionObjectEffects.saveSubmissionSectionSuccess$).toBeObservable(expected);
+ expect(notificationsServiceStub.warning).not.toHaveBeenCalled();
+ });
+
+ it('should detect new sections but not notify for it', () => {
+ store.nextState({
+ submission: {
+ objects: submissionState
+ }
+ } as any);
+
+ const response = [Object.assign({}, mockSubmissionRestResponse[0], {
+ sections: mockSectionsDataTwo,
+ errors: mockSectionsErrors
+ })];
+ actions = hot('--a-', {
+ a: {
+ type: SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
+ payload: {
+ submissionId: submissionId,
+ submissionObject: response,
+ }
+ }
+ });
+
+ const errorsList = parseSectionErrors(mockSectionsErrors);
+ const expected = cold('--(bcde)-', {
+ b: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpageone',
+ mockSectionsDataTwo.traditionalpageone as any,
+ []
+ ),
+ c: new UpdateSectionDataAction(
+ submissionId,
+ 'traditionalpagetwo',
+ mockSectionsDataTwo.traditionalpagetwo as any,
+ errorsList.traditionalpagetwo || []
+ ),
+ d: new UpdateSectionDataAction(
+ submissionId,
+ 'license',
+ mockSectionsDataTwo.license as any,
+ errorsList.license || []
+ ),
+ e: new UpdateSectionDataAction(
+ submissionId,
+ 'upload',
+ mockSectionsDataTwo.upload as any,
+ errorsList.upload || []
+ ),
+ });
+
+ expect(submissionObjectEffects.saveSubmissionSectionSuccess$).toBeObservable(expected);
+ expect(submissionServiceStub.notifyNewSection).not.toHaveBeenCalled();
+ });
+
+ });
+
describe('saveSection$', () => {
it('should return a SAVE_SUBMISSION_SECTION_FORM_SUCCESS action on success', () => {
actions = hot('--a-', {
diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts
index 0af7b6c275..4f6563c662 100644
--- a/src/app/submission/objects/submission-objects.effects.ts
+++ b/src/app/submission/objects/submission-objects.effects.ts
@@ -52,12 +52,14 @@ import {
UpdateSectionDataAction,
UpdateSectionDataSuccessAction
} from './submission-objects.actions';
-import { SubmissionObjectEntry, SubmissionSectionObject } from './submission-objects.reducer';
+import {SubmissionObjectEntry, SubmissionSectionError, SubmissionSectionObject} from './submission-objects.reducer';
import { Item } from '../../core/shared/item.model';
import { RemoteData } from '../../core/data/remote-data';
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
import { SubmissionObjectDataService } from '../../core/submission/submission-object-data.service';
import { followLink } from '../../shared/utils/follow-link-config.model';
+import parseSectionErrorPaths, {SectionErrorPath} from '../utils/parseSectionErrorPaths';
+import { FormState } from '../../shared/form/form.reducer';
@Injectable()
export class SubmissionObjectEffects {
@@ -132,7 +134,7 @@ export class SubmissionObjectEffects {
this.submissionService.getSubmissionObjectLinkName(),
action.payload.submissionId,
'sections').pipe(
- map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response)),
+ map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual)),
catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId))));
}));
@@ -154,10 +156,24 @@ export class SubmissionObjectEffects {
* Call parseSaveResponse and dispatch actions
*/
@Effect() saveSubmissionSuccess$ = this.actions$.pipe(
- ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS),
+ ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS),
withLatestFrom(this.store$),
- map(([action, currentState]: [SaveSubmissionFormSuccessAction | SaveSubmissionSectionFormSuccessAction, any]) => {
- return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId], action.payload.submissionObject, action.payload.submissionId, action.payload.notify);
+ map(([action, currentState]: [SaveSubmissionFormSuccessAction, any]) => {
+ return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId],
+ action.payload.submissionObject, action.payload.submissionId, currentState.forms, action.payload.notify);
+ }),
+ mergeMap((actions) => observableFrom(actions)));
+
+ /**
+ * Call parseSaveResponse and dispatch actions.
+ * Notification system is forced to be disabled.
+ */
+ @Effect() saveSubmissionSectionSuccess$ = this.actions$.pipe(
+ ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS),
+ withLatestFrom(this.store$),
+ map(([action, currentState]: [SaveSubmissionSectionFormSuccessAction, any]) => {
+ return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId],
+ action.payload.submissionObject, action.payload.submissionId, currentState.forms, false);
}),
mergeMap((actions) => observableFrom(actions)));
@@ -200,7 +216,8 @@ export class SubmissionObjectEffects {
return new DepositSubmissionAction(action.payload.submissionId);
} else {
this.notificationsService.warning(null, this.translate.get('submission.sections.general.sections_not_valid'));
- return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId], response, action.payload.submissionId);
+ return this.parseSaveResponse((currentState.submission as SubmissionState).objects[action.payload.submissionId],
+ response, action.payload.submissionId, currentState.forms);
}
}),
catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId))));
@@ -280,7 +297,7 @@ export class SubmissionObjectEffects {
return item$.pipe(
map((item: Item) => item.metadata),
filter((metadata) => !isEqual(action.payload.data, metadata)),
- map((metadata: any) => new UpdateSectionDataAction(action.payload.submissionId, action.payload.sectionId, metadata, action.payload.errors))
+ map((metadata: any) => new UpdateSectionDataAction(action.payload.submissionId, action.payload.sectionId, metadata, action.payload.errors, action.payload.metadata))
);
} else {
return observableOf(new UpdateSectionDataSuccessAction());
@@ -353,6 +370,7 @@ export class SubmissionObjectEffects {
currentState: SubmissionObjectEntry,
response: SubmissionObject[],
submissionId: string,
+ forms,
notify: boolean = true): SubmissionObjectAction[] {
const mappedActions = [];
@@ -392,10 +410,54 @@ export class SubmissionObjectEffects {
if (notify && !currentState.sections[sectionId].enabled) {
this.submissionService.notifyNewSection(submissionId, sectionId, currentState.sections[sectionId].sectionType);
}
- mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, sectionErrors));
+
+ const sectionForm = getForm(forms, currentState, sectionId);
+ const filteredErrors = filterErrors(sectionForm, sectionErrors, currentState.sections[sectionId].sectionType, notify);
+ mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, filteredErrors));
}
});
}
return mappedActions;
}
}
+
+function getForm(forms, currentState, sectionId) {
+ if (!forms) {
+ return null;
+ }
+ const formId = currentState.sections[sectionId].formId;
+ return forms[formId];
+}
+
+/**
+ * Filter sectionErrors accordingly to this rules:
+ * 1. if notifications are enabled return all errors
+ * 2. if sectionType is different from 'submission-form' return all errors
+ * 3. otherwise return errors only for those fields marked as touched inside the section form
+ * @param sectionForm
+ * The form related to the section
+ * @param sectionErrors
+ * The section errors array
+ * @param sectionType
+ * The section type
+ * @param notify
+ * Whether notifications are enabled
+ */
+function filterErrors(sectionForm: FormState, sectionErrors: SubmissionSectionError[], sectionType: string, notify: boolean): SubmissionSectionError[] {
+ if (notify || sectionType !== SectionsType.SubmissionForm) {
+ return sectionErrors;
+ }
+ if (!sectionForm || !sectionForm.touched) {
+ return [];
+ }
+ const filteredErrors = [];
+ sectionErrors.forEach((error: SubmissionSectionError) => {
+ const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path);
+ errorPaths.forEach((path: SectionErrorPath) => {
+ if (path.fieldId && sectionForm.touched[path.fieldId]) {
+ filteredErrors.push(error);
+ }
+ });
+ });
+ return filteredErrors;
+}
diff --git a/src/app/submission/objects/submission-objects.reducer.spec.ts b/src/app/submission/objects/submission-objects.reducer.spec.ts
index 0c585e4bca..0431cdff79 100644
--- a/src/app/submission/objects/submission-objects.reducer.spec.ts
+++ b/src/app/submission/objects/submission-objects.reducer.spec.ts
@@ -335,6 +335,17 @@ describe('submissionReducer test suite', () => {
expect(newState[826].sections.traditionalpageone.data).toEqual(data);
});
+ it('should update submission section metadata properly', () => {
+ const data = {
+ } as any;
+ const metadata = ['dc.title', 'dc.contributor.author'];
+
+ const action = new UpdateSectionDataAction(submissionId, 'traditionalpageone', data, [], metadata);
+ const newState = submissionObjectReducer(initState, action);
+
+ expect(newState[826].sections.traditionalpageone.metadata).toEqual(metadata);
+ });
+
it('should add submission section errors properly', () => {
const errors = [
{
diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts
index 098160c737..9d2030ce25 100644
--- a/src/app/submission/objects/submission-objects.reducer.ts
+++ b/src/app/submission/objects/submission-objects.reducer.ts
@@ -30,6 +30,7 @@ import {
SaveSubmissionSectionFormSuccessAction,
SectionStatusChangeAction,
SetActiveSectionAction,
+ SetSectionFormId,
SubmissionObjectAction,
SubmissionObjectActionTypes,
UpdateSectionDataAction
@@ -85,6 +86,11 @@ export interface SubmissionSectionObject {
*/
enabled: boolean;
+ /**
+ * The list of the metadata ids of the section.
+ */
+ metadata: string[];
+
/**
* The section data object
*/
@@ -104,6 +110,11 @@ export interface SubmissionSectionObject {
* A boolean representing if this section is valid
*/
isValid: boolean;
+
+ /**
+ * The formId related to this section
+ */
+ formId: string;
}
/**
@@ -258,6 +269,10 @@ export function submissionObjectReducer(state = initialState, action: Submission
return initSection(state, action as InitSectionAction);
}
+ case SubmissionObjectActionTypes.SET_SECTION_FORM_ID: {
+ return setSectionFormId(state, action as SetSectionFormId);
+ }
+
case SubmissionObjectActionTypes.ENABLE_SECTION: {
return changeSectionState(state, action as EnableSectionAction, true);
}
@@ -641,6 +656,33 @@ function initSection(state: SubmissionObjectState, action: InitSectionAction): S
}
}
+/**
+ * Set a section form id.
+ *
+ * @param state
+ * the current state
+ * @param action
+ * an SetSectionFormId
+ * @return SubmissionObjectState
+ * the new state
+ */
+function setSectionFormId(state: SubmissionObjectState, action: SetSectionFormId): SubmissionObjectState {
+ if (hasValue(state[ action.payload.submissionId ])) {
+ return Object.assign({}, state, {
+ [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], {
+ sections: Object.assign({}, state[ action.payload.submissionId ].sections, {
+ [ action.payload.sectionId ]: {
+ ...state[ action.payload.submissionId ].sections [action.payload.sectionId],
+ formId: action.payload.formId
+ }
+ })
+ })
+ });
+ } else {
+ return state;
+ }
+}
+
/**
* Update section's data.
*
@@ -653,14 +695,15 @@ function initSection(state: SubmissionObjectState, action: InitSectionAction): S
*/
function updateSectionData(state: SubmissionObjectState, action: UpdateSectionDataAction): SubmissionObjectState {
if (isNotEmpty(state[ action.payload.submissionId ])
- && isNotEmpty(state[ action.payload.submissionId ].sections[ action.payload.sectionId])) {
+ && isNotEmpty(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, {
[ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections [ action.payload.sectionId ], {
enabled: true,
data: action.payload.data,
- errors: action.payload.errors
+ errors: action.payload.errors,
+ metadata: reduceSectionMetadata(action.payload.metadata, state[ action.payload.submissionId ].sections [ action.payload.sectionId ].metadata)
})
})
})
@@ -670,6 +713,24 @@ function updateSectionData(state: SubmissionObjectState, action: UpdateSectionDa
}
}
+/**
+ * Updates the state of the section metadata only when a new value is provided.
+ * Keep the existent otherwise.
+ * @param newMetadata
+ * @param oldMetadata
+ * @return
+ * new sectionMetadata value
+ */
+function reduceSectionMetadata(newMetadata: string[], oldMetadata: string[]): string[] {
+ if (newMetadata) {
+ return newMetadata;
+ }
+ if (oldMetadata) {
+ return [...oldMetadata];
+ }
+ return undefined;
+}
+
/**
* Set a section state.
*
diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts
index 79e24dd451..d649f985ca 100644
--- a/src/app/submission/sections/form/section-form.component.spec.ts
+++ b/src/app/submission/sections/form/section-form.component.spec.ts
@@ -287,6 +287,7 @@ describe('SubmissionSectionformComponent test suite', () => {
'dc.title': [new FormFieldMetadataValueObject('test')]
};
compAsAny.formData = {};
+ compAsAny.sectionMetadata = ['dc.title'];
expect(comp.hasMetadataEnrichment(newSectionData)).toBeTruthy();
});
@@ -296,7 +297,16 @@ describe('SubmissionSectionformComponent test suite', () => {
'dc.title': [new FormFieldMetadataValueObject('test')]
};
compAsAny.formData = newSectionData;
+ compAsAny.sectionMetadata = ['dc.title'];
+ expect(comp.hasMetadataEnrichment(newSectionData)).toBeFalsy();
+ });
+ it('should return false when metadata has Metadata Enrichment but not belonging to sectionMetadata', () => {
+ const newSectionData = {
+ 'dc.title': [new FormFieldMetadataValueObject('test')]
+ };
+ compAsAny.formData = newSectionData;
+ compAsAny.sectionMetadata = [];
expect(comp.hasMetadataEnrichment(newSectionData)).toBeFalsy();
});
@@ -310,6 +320,7 @@ describe('SubmissionSectionformComponent test suite', () => {
comp.sectionData.data = {};
comp.sectionData.errors = [];
compAsAny.formData = {};
+ compAsAny.sectionMetadata = ['dc.title'];
comp.updateForm(sectionData, sectionError);
@@ -329,10 +340,11 @@ describe('SubmissionSectionformComponent test suite', () => {
comp.sectionData.data = {};
comp.sectionData.errors = [];
compAsAny.formData = sectionData;
+ compAsAny.sectionMetadata = ['dc.title'];
comp.updateForm(sectionData, parsedSectionErrors);
- expect(comp.initForm).toHaveBeenCalled();
+ expect(comp.initForm).not.toHaveBeenCalled();
expect(comp.checksForErrors).toHaveBeenCalled();
expect(comp.sectionData.data).toEqual(sectionData);
});
diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts
index 90c2c0c38c..1f37be89e2 100644
--- a/src/app/submission/sections/form/section-form.component.ts
+++ b/src/app/submission/sections/form/section-form.component.ts
@@ -13,7 +13,7 @@ import {
tap
} from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
-import { isEqual } from 'lodash';
+import { isEqual, findIndex } from 'lodash';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
import { FormComponent } from '../../../shared/form/form.component';
@@ -101,6 +101,12 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
*/
protected formData: any = Object.create({});
+ /**
+ * Store the
+ * @protected
+ */
+ protected sectionMetadata: string[];
+
/**
* The [JsonPatchOperationPathCombiner] object
* @type {JsonPatchOperationPathCombiner}
@@ -168,6 +174,7 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
onSectionInit() {
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id);
this.formId = this.formService.getUniqueId(this.sectionData.id);
+ this.sectionService.dispatchSetSectionFormId(this.submissionId, this.sectionData.id, this.formId);
this.formConfigService.findByHref(this.sectionData.config).pipe(
map((configData: RemoteData) => configData.payload),
tap((config: SubmissionFormsModel) => this.formConfig = config),
@@ -230,16 +237,25 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
* the section data retrieved from the server
*/
hasMetadataEnrichment(sectionData: WorkspaceitemSectionFormObject): boolean {
+
+ const sectionDataToCheck = {};
+ Object.keys(sectionData).forEach((key) => {
+ if (this.sectionMetadata && this.sectionMetadata.includes(key)) {
+ sectionDataToCheck[key] = sectionData[key];
+ }
+ })
+
const diffResult = [];
// compare current form data state with section data retrieved from store
- const diffObj = difference(sectionData, this.formData);
+ const diffObj = difference(sectionDataToCheck, this.formData);
// iterate over differences to check whether they are actually different
Object.keys(diffObj)
.forEach((key) => {
diffObj[key].forEach((value) => {
- if (value.hasOwnProperty('value')) {
+ // the findIndex extra check excludes values already present in the form but in different positions
+ if (value.hasOwnProperty('value') && findIndex(this.formData[key], { value: value.value }) < 0) {
diffResult.push(value);
}
});
@@ -262,6 +278,9 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
sectionData,
this.submissionService.getSubmissionScope()
);
+ const sectionMetadata = this.sectionService.computeSectionConfiguredMetadata(this.formConfig);
+ this.sectionService.updateSectionData(this.submissionId, this.sectionData.id, sectionData, [], sectionMetadata);
+
} catch (e) {
const msg: string = this.translate.instant('error.submission.sections.init-form-error') + e.toString();
const sectionError: SubmissionSectionError = {
@@ -283,15 +302,19 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
*/
updateForm(sectionData: WorkspaceitemSectionFormObject, errors: SubmissionSectionError[]): void {
- if (hasValue(sectionData) && !isEqual(sectionData, this.sectionData.data)) {
+ if (isNotEmpty(sectionData) && !isEqual(sectionData, this.sectionData.data)) {
this.sectionData.data = sectionData;
- this.isUpdating = true;
- this.formModel = null;
- this.cdr.detectChanges();
- this.initForm(sectionData);
- this.checksForErrors(errors);
- this.isUpdating = false;
- this.cdr.detectChanges();
+ if (this.hasMetadataEnrichment(sectionData)) {
+ this.isUpdating = true;
+ this.formModel = null;
+ this.cdr.detectChanges();
+ this.initForm(sectionData);
+ this.checksForErrors(errors);
+ this.isUpdating = false;
+ this.cdr.detectChanges();
+ } else if (isNotEmpty(errors) || isNotEmpty(this.sectionData.errors)) {
+ this.checksForErrors(errors);
+ }
} else if (isNotEmpty(errors) || isNotEmpty(this.sectionData.errors)) {
this.checksForErrors(errors);
}
@@ -338,6 +361,7 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
distinctUntilChanged())
.subscribe((sectionState: SubmissionSectionObject) => {
this.fieldsOnTheirWayToBeRemoved = new Map();
+ this.sectionMetadata = sectionState.metadata;
this.updateForm(sectionState.data as WorkspaceitemSectionFormObject, sectionState.errors);
})
)
diff --git a/src/app/submission/sections/sections.service.spec.ts b/src/app/submission/sections/sections.service.spec.ts
index 5c7bff13ce..75b6dfe67e 100644
--- a/src/app/submission/sections/sections.service.spec.ts
+++ b/src/app/submission/sections/sections.service.spec.ts
@@ -380,4 +380,25 @@ describe('SectionsService test suite', () => {
expect(store.dispatch).toHaveBeenCalledWith(new UpdateSectionDataAction(submissionId, sectionId, data, []));
});
});
+
+ describe('computeSectionConfiguredMetadata', () => {
+ it('should return the configured metadata of the section from the form configuration', () => {
+
+ const formConfig = {
+ rows: [{
+ fields: [{
+ selectableMetadata: [{
+ metadata: 'dc.contributor.author'
+ }]
+ }]
+ }]
+ }
+
+ const expectedConfiguredMetadata = [ 'dc.contributor.author' ];
+
+ const configuredMetadata = service.computeSectionConfiguredMetadata(formConfig as any);
+
+ expect(configuredMetadata).toEqual(expectedConfiguredMetadata);
+ });
+ });
});
diff --git a/src/app/submission/sections/sections.service.ts b/src/app/submission/sections/sections.service.ts
index 5aa3c1d3ea..a38d7d6981 100644
--- a/src/app/submission/sections/sections.service.ts
+++ b/src/app/submission/sections/sections.service.ts
@@ -15,6 +15,7 @@ import {
InertSectionErrorsAction,
RemoveSectionErrorsAction,
SectionStatusChangeAction,
+ SetSectionFormId,
UpdateSectionDataAction
} from '../objects/submission-objects.actions';
import {
@@ -36,6 +37,8 @@ import { SubmissionService } from '../submission.service';
import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model';
import { SectionsType } from './sections-type';
import { normalizeSectionData } from '../../core/submission/submission-response-parsing.service';
+import { SubmissionFormsModel } from '../../core/config/models/config-submission-forms.model';
+import { parseReviver } from '@ng-dynamic-forms/core';
/**
* A service that provides methods used in submission process.
@@ -133,6 +136,18 @@ export class SectionsService {
this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId));
}
+ /**
+ * Dispatch a new [SetSectionFormId]
+ * The submission id
+ * @param sectionId
+ * The section id
+ * @param formId
+ * The form id
+ */
+ public dispatchSetSectionFormId(submissionId, sectionId, formId) {
+ this.store.dispatch(new SetSectionFormId(submissionId, sectionId, formId));
+ }
+
/**
* Return the data object for the specified section
*
@@ -335,8 +350,10 @@ export class SectionsService {
* The section data
* @param errors
* The list of section errors
+ * @param metadata
+ * The section metadata
*/
- public updateSectionData(submissionId: string, sectionId: string, data: WorkspaceitemSectionDataType, errors: SubmissionSectionError[] = []) {
+ public updateSectionData(submissionId: string, sectionId: string, data: WorkspaceitemSectionDataType, errors: SubmissionSectionError[] = [], metadata?: string[]) {
if (isNotEmpty(data)) {
const isAvailable$ = this.isSectionAvailable(submissionId, sectionId);
const isEnabled$ = this.isSectionEnabled(submissionId, sectionId);
@@ -345,7 +362,7 @@ export class SectionsService {
take(1),
filter(([available, enabled]: [boolean, boolean]) => available))
.subscribe(([available, enabled]: [boolean, boolean]) => {
- this.store.dispatch(new UpdateSectionDataAction(submissionId, sectionId, data, errors));
+ this.store.dispatch(new UpdateSectionDataAction(submissionId, sectionId, data, errors, metadata));
});
}
}
@@ -377,4 +394,30 @@ export class SectionsService {
public setSectionStatus(submissionId: string, sectionId: string, status: boolean) {
this.store.dispatch(new SectionStatusChangeAction(submissionId, sectionId, status));
}
+
+ /**
+ * Compute the list of selectable metadata for the section configuration.
+ * @param formConfig
+ */
+ public computeSectionConfiguredMetadata(formConfig: string | SubmissionFormsModel): string[] {
+ const metadata = [];
+ const rawData = typeof formConfig === 'string' ? JSON.parse(formConfig, parseReviver) : formConfig;
+ if (rawData.rows && !isEmpty(rawData.rows)) {
+ rawData.rows.forEach((currentRow) => {
+ if (currentRow.fields && !isEmpty(currentRow.fields)) {
+ currentRow.fields.forEach((field) => {
+ if (field.selectableMetadata && !isEmpty(field.selectableMetadata)) {
+ field.selectableMetadata.forEach((selectableMetadata) => {
+ if (!metadata.includes(selectableMetadata.metadata)) {
+ metadata.push(selectableMetadata.metadata);
+ }
+ })
+ }
+ })
+ }
+ });
+ }
+ return metadata;
+ }
+
}
diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts
index 5816c97bde..579d1c0624 100644
--- a/src/app/submission/submission.service.spec.ts
+++ b/src/app/submission/submission.service.spec.ts
@@ -46,6 +46,8 @@ import { SearchService } from '../core/shared/search/search.service';
import { Item } from '../core/shared/item.model';
import { storeModuleConfig } from '../app.reducer';
import { environment } from '../../environments/environment';
+import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service';
+import { SubmissionJsonPatchOperationsServiceStub } from '../shared/testing/submission-json-patch-operations-service.stub';
describe('SubmissionService test suite', () => {
const collectionId = '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f';
@@ -345,6 +347,7 @@ describe('SubmissionService test suite', () => {
const router = new RouterMock();
const selfUrl = 'https://rest.api/dspace-spring-rest/api/submission/workspaceitems/826';
const submissionDefinition: any = mockSubmissionDefinition;
+ const submissionJsonPatchOperationsService = new SubmissionJsonPatchOperationsServiceStub();
let scheduler: TestScheduler;
let service: SubmissionService;
@@ -371,6 +374,7 @@ describe('SubmissionService test suite', () => {
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
{ provide: SearchService, useValue: searchService },
{ provide: RequestService, useValue: requestServce },
+ { provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsService },
NotificationsService,
RouteService,
SubmissionService,
@@ -487,11 +491,18 @@ describe('SubmissionService test suite', () => {
describe('dispatchSave', () => {
it('should dispatch a new SaveSubmissionFormAction', () => {
- service.dispatchSave(submissionId,);
+ service.dispatchSave(submissionId);
const expected = new SaveSubmissionFormAction(submissionId);
expect((service as any).store.dispatch).toHaveBeenCalledWith(expected);
});
+
+ it('should dispatch a new SaveSubmissionFormAction with manual flag', () => {
+ service.dispatchSave(submissionId, true);
+ const expected = new SaveSubmissionFormAction(submissionId, true);
+
+ expect((service as any).store.dispatch).toHaveBeenCalledWith(expected);
+ });
});
describe('dispatchSaveForLater', () => {
@@ -746,6 +757,20 @@ describe('SubmissionService test suite', () => {
});
});
+ describe('hasUnsavedModification', () => {
+ it('should call jsonPatchOperationService hasPendingOperation observable', () => {
+ (service as any).jsonPatchOperationService.hasPendingOperations = jasmine.createSpy('hasPendingOperations')
+ .and.returnValue(observableOf(true));
+
+ scheduler = getTestScheduler();
+ scheduler.schedule(() => service.hasUnsavedModification());
+ scheduler.flush();
+
+ expect((service as any).jsonPatchOperationService.hasPendingOperations).toHaveBeenCalledWith('sections');
+
+ });
+ });
+
describe('isSectionHidden', () => {
it('should return true/false when section is hidden/visible', () => {
let section: any = {
@@ -915,8 +940,15 @@ describe('SubmissionService test suite', () => {
});
describe('startAutoSave', () => {
+
+ let environmentAutoSaveTimerOriginalValue;
+
+ beforeEach(() => {
+ environmentAutoSaveTimerOriginalValue = environment.submission.autosave.timer;
+ });
+
it('should start Auto Save', fakeAsync(() => {
- const duration = environment.submission.autosave.timer * (1000 * 60);
+ const duration = environment.submission.autosave.timer;
service.startAutoSave('826');
const sub = (service as any).timer$.subscribe();
@@ -930,6 +962,19 @@ describe('SubmissionService test suite', () => {
sub.unsubscribe();
(service as any).autoSaveSub.unsubscribe();
}));
+
+ it('should not start Auto Save if timer is 0', fakeAsync(() => {
+ environment.submission.autosave.timer = 0;
+
+ service.startAutoSave('826');
+
+ expect((service as any).autoSaveSub).toBeUndefined();
+ }));
+
+ afterEach(() => {
+ environment.submission.autosave.timer = environmentAutoSaveTimerOriginalValue;
+ })
+
});
describe('stopAutoSave', () => {
diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts
index c9b1d41b40..87d6cbc9a4 100644
--- a/src/app/submission/submission.service.ts
+++ b/src/app/submission/submission.service.ts
@@ -45,6 +45,7 @@ import { RequestService } from '../core/data/request.service';
import { SearchService } from '../core/shared/search/search.service';
import { Item } from '../core/shared/item.model';
import { environment } from '../../environments/environment';
+import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service';
/**
* A service that provides methods used in submission process.
@@ -82,7 +83,8 @@ export class SubmissionService {
protected store: Store,
protected translate: TranslateService,
protected searchService: SearchService,
- protected requestService: RequestService) {
+ protected requestService: RequestService,
+ protected jsonPatchOperationService: SubmissionJsonPatchOperationsService) {
}
/**
@@ -209,12 +211,14 @@ export class SubmissionService {
*
* @param submissionId
* The submission id
+ * @param manual
+ * whether is a manual save, default false
*/
- dispatchSave(submissionId) {
+ dispatchSave(submissionId, manual?: boolean) {
this.getSubmissionSaveProcessingStatus(submissionId).pipe(
find((isPending: boolean) => !isPending)
).subscribe(() => {
- this.store.dispatch(new SaveSubmissionFormAction(submissionId));
+ this.store.dispatch(new SaveSubmissionFormAction(submissionId, manual));
})
}
@@ -427,6 +431,16 @@ export class SubmissionService {
startWith(false));
}
+ /**
+ * Return whether submission unsaved modification are present
+ *
+ * @return Observable
+ * observable with submission unsaved modification presence
+ */
+ hasUnsavedModification(): Observable {
+ return this.jsonPatchOperationService.hasPendingOperations('sections');
+ }
+
/**
* Return the visibility status of the specified section
*
@@ -562,9 +576,12 @@ export class SubmissionService {
*/
startAutoSave(submissionId) {
this.stopAutoSave();
+ if (environment.submission.autosave.timer === 0) {
+ return;
+ }
+
// AUTOSAVE submission
- // Retrieve interval from config and convert to milliseconds
- const duration = environment.submission.autosave.timer * (1000 * 60);
+ const duration = environment.submission.autosave.timer;
// Dispatch save action after given duration
this.timer$ = observableTimer(duration, duration);
this.autoSaveSub = this.timer$
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index de764f4de9..603acb3f48 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -462,14 +462,10 @@
"admin.search.item.move": "Move",
- "admin.search.item.private": "Private",
-
"admin.search.item.reinstate": "Reinstate",
"admin.search.item.withdraw": "Withdraw",
- "admin.search.item.withdrawn": "Withdrawn",
-
"admin.search.title": "Administrative Search",
@@ -1345,12 +1341,24 @@
+ "item.alerts.private": "This item is private",
+
+ "item.alerts.withdrawn": "This item has been withdrawn",
+
+
+
"item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.",
"item.edit.authorizations.title": "Edit item's Policies",
+ "item.badge.private": "Private",
+
+ "item.badge.withdrawn": "Withdrawn",
+
+
+
"item.bitstreams.upload.bundle": "Bundle",
"item.bitstreams.upload.bundle.placeholder": "Select a bundle",
diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts
index eb961a38eb..80b383a801 100644
--- a/src/environments/environment.common.ts
+++ b/src/environments/environment.common.ts
@@ -65,9 +65,9 @@ export const environment: GlobalConfig = {
submission: {
autosave: {
// NOTE: which metadata trigger an autosave
- metadata: ['dc.title', 'dc.identifier.doi', 'dc.identifier.pmid', 'dc.identifier.arxiv'],
+ metadata: [],
// NOTE: every how many minutes submission is saved automatically
- timer: 5
+ timer: 0
},
icons: {
metadata: [