diff --git a/Dockerfile b/Dockerfile index 2d98971112..a7c1640d0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,9 @@ EXPOSE 4000 # We run yarn install with an increased network timeout (5min) to avoid "ESOCKETTIMEDOUT" errors from hub.docker.com # See, for example https://github.com/yarnpkg/yarn/issues/5540 RUN yarn install --network-timeout 300000 -CMD yarn run start:dev + +# On startup, run in DEVELOPMENT mode (this defaults to live reloading enabled, etc). +# Listen / accept connections from all IP addresses. +# NOTE: At this time it is only possible to run Docker container in Production mode +# if you have a public IP. See https://github.com/DSpace/dspace-angular/issues/1485 +CMD yarn serve --host 0.0.0.0 diff --git a/cypress/integration/submission.spec.ts b/cypress/integration/submission.spec.ts index c877479f44..009c50115b 100644 --- a/cypress/integration/submission.spec.ts +++ b/cypress/integration/submission.spec.ts @@ -42,6 +42,7 @@ describe('New Submission page', () => { cy.get('button#deposit').click(); // A warning alert should display. + cy.get('ds-notification div.alert-success').should('not.exist'); cy.get('ds-notification div.alert-warning').should('be.visible'); // First section should have an exclamation error in the header diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index b0091da86d..fef6904177 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -1,21 +1,35 @@ import { Component, HostListener, Injector, OnInit } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { combineLatest, combineLatest as observableCombineLatest, Observable, BehaviorSubject } from 'rxjs'; -import { debounceTime, first, map, take, filter, distinctUntilChanged, withLatestFrom } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest as observableCombineLatest, combineLatest, Observable } from 'rxjs'; +import { debounceTime, distinctUntilChanged, filter, first, map, take, withLatestFrom } from 'rxjs/operators'; import { AuthService } from '../../core/auth/auth.service'; import { - ScriptDataService, + METADATA_EXPORT_SCRIPT_NAME, METADATA_IMPORT_SCRIPT_NAME, - METADATA_EXPORT_SCRIPT_NAME + ScriptDataService } from '../../core/data/processes/script-data.service'; import { slideHorizontal, slideSidebar } from '../../shared/animations/slide'; -import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; -import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; -import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; -import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; -import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; -import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; -import { ExportMetadataSelectorComponent } from '../../shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; +import { + CreateCollectionParentSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; +import { + CreateCommunityParentSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; +import { + CreateItemParentSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; +import { + EditCollectionSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; +import { + EditCommunitySelectorComponent +} from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; +import { + EditItemSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; +import { + ExportMetadataSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model'; import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model'; import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model'; @@ -26,7 +40,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { MenuID } from '../../shared/menu/menu-id.model'; import { MenuItemType } from '../../shared/menu/menu-item-type.model'; -import { Router, ActivatedRoute } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; /** * Component representing the admin sidebar @@ -86,12 +100,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { * Set and calculate all initial values of the instance variables */ ngOnInit(): void { - this.createMenu(); super.ngOnInit(); this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth'); this.authService.isAuthenticated() .subscribe((loggedIn: boolean) => { if (loggedIn) { + this.createMenu(); this.menuService.showMenu(this.menuID); } }); @@ -368,10 +382,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { ]; menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection)); - observableCombineLatest( + observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME) - ).pipe( + ]).pipe( filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists), take(1) ).subscribe(() => { @@ -430,10 +444,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { shouldPersistOnRouteChange: true }))); - observableCombineLatest( + observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME) - ).pipe( + ]).pipe( filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists), take(1) ).subscribe(() => { @@ -559,10 +573,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { * Create menu sections dependent on whether or not the current user can manage access control groups */ createAccessControlMenuSections() { - observableCombineLatest( + observableCombineLatest([ this.authorizationService.isAuthorized(FeatureID.AdministratorOf), this.authorizationService.isAuthorized(FeatureID.CanManageGroups) - ).subscribe(([isSiteAdmin, canManageGroups]) => { + ]).subscribe(([isSiteAdmin, canManageGroups]) => { const menuList = [ /* Access Control */ { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 669411d9aa..db217ce161 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -201,7 +201,6 @@ export class AppComponent implements OnInit, AfterViewInit { if (event instanceof NavigationStart) { resolveEndFound = false; this.isRouteLoading$.next(true); - this.isThemeLoading$.next(true); } else if (event instanceof ResolveEnd) { resolveEndFound = true; const activatedRouteSnapShot: ActivatedRouteSnapshot = event.state.root; diff --git a/src/app/root/root.component.html b/src/app/root/root.component.html index c8b43796cb..d187782094 100644 --- a/src/app/root/root.component.html +++ b/src/app/root/root.component.html @@ -1,4 +1,4 @@ -
+
- -
- -
-
+ +
+ +
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts index e04f537b3e..baef19f14a 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts @@ -4,6 +4,7 @@ import { FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs'; import { SearchEvent } from '../eperson-group-list.component'; +import { isNotNull } from '../../../../empty.util'; /** * A component used to show a search box for groups. @@ -54,7 +55,7 @@ export class GroupSearchBoxComponent { submit(data: any) { const event: SearchEvent = { scope: '', - query: data.query + query: isNotNull(data) ? data.query : '' }; this.search.emit(event); } diff --git a/src/app/submission/objects/submission-objects.actions.ts b/src/app/submission/objects/submission-objects.actions.ts index dd6adf152e..9182611e47 100644 --- a/src/app/submission/objects/submission-objects.actions.ts +++ b/src/app/submission/objects/submission-objects.actions.ts @@ -421,7 +421,8 @@ export class SaveSubmissionFormSuccessAction implements Action { payload: { submissionId: string; submissionObject: SubmissionObject[]; - notify?: boolean + showNotifications?: boolean; + showErrors?: boolean; }; /** @@ -431,9 +432,13 @@ export class SaveSubmissionFormSuccessAction implements Action { * the submission's ID * @param submissionObject * the submission's Object + * @param showNotifications + * a boolean representing if to show notifications on save + * @param showErrors + * a boolean representing if to show errors on save */ - constructor(submissionId: string, submissionObject: SubmissionObject[], notify?: boolean) { - this.payload = { submissionId, submissionObject, notify }; + constructor(submissionId: string, submissionObject: SubmissionObject[], showNotifications?: boolean, showErrors?: boolean) { + this.payload = { submissionId, submissionObject, showNotifications, showErrors }; } } diff --git a/src/app/submission/objects/submission-objects.effects.spec.ts b/src/app/submission/objects/submission-objects.effects.spec.ts index b2bc054287..a1bb878aa5 100644 --- a/src/app/submission/objects/submission-objects.effects.spec.ts +++ b/src/app/submission/objects/submission-objects.effects.spec.ts @@ -237,6 +237,7 @@ describe('SubmissionObjectEffects test suite', () => { b: new SaveSubmissionFormSuccessAction( submissionId, mockSubmissionRestResponse as any, + true, true ) }); @@ -260,6 +261,7 @@ describe('SubmissionObjectEffects test suite', () => { b: new SaveSubmissionFormSuccessAction( submissionId, mockSubmissionRestResponse as any, + false, false ) }); @@ -884,6 +886,7 @@ describe('SubmissionObjectEffects test suite', () => { }); expect(submissionObjectEffects.saveAndDeposit$).toBeObservable(expected); + expect(notificationsServiceStub.warning).not.toHaveBeenCalled(); }); it('should return a SAVE_SUBMISSION_FORM_SUCCESS action when there are errors', () => { @@ -910,10 +913,11 @@ describe('SubmissionObjectEffects test suite', () => { submissionJsonPatchOperationsServiceStub.jsonPatchByResourceType.and.returnValue(observableOf(response)); const expected = cold('--b-', { - b: new SaveSubmissionFormSuccessAction(submissionId, response as any[]) + b: new SaveSubmissionFormSuccessAction(submissionId, response as any[], false, true) }); expect(submissionObjectEffects.saveAndDeposit$).toBeObservable(expected); + expect(notificationsServiceStub.warning).toHaveBeenCalled(); }); it('should catch errors and return a SAVE_SUBMISSION_FORM_ERROR', () => { diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index 7a3301e0f3..e79670306f 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -1,4 +1,3 @@ -import { WorkspaceitemSectionSherpaPoliciesObject } from './../../core/submission/models/workspaceitem-section-sherpa-policies.model'; import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; @@ -44,7 +43,7 @@ import { UpdateSectionDataAction, UpdateSectionDataSuccessAction } from './submission-objects.actions'; -import { SubmissionObjectEntry } from './submission-objects.reducer'; +import { SubmissionObjectEntry} from './submission-objects.reducer'; import { Item } from '../../core/shared/item.model'; import { RemoteData } from '../../core/data/remote-data'; import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; @@ -61,12 +60,12 @@ export class SubmissionObjectEffects { /** * Dispatch a [InitSectionAction] for every submission sections and dispatch a [CompleteInitSubmissionFormAction] */ - loadForm$ = createEffect(() => this.actions$.pipe( + loadForm$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.INIT_SUBMISSION_FORM), map((action: InitSubmissionFormAction) => { const definition = action.payload.submissionDefinition; const mappedActions = []; - definition.sections.page.forEach((sectionDefinition: any, index) => { + definition.sections.page.forEach((sectionDefinition: any) => { const selfLink = sectionDefinition._links.self.href || sectionDefinition._links.self; const sectionId = selfLink.substr(selfLink.lastIndexOf('/') + 1); const config = sectionDefinition._links.config ? (sectionDefinition._links.config.href || sectionDefinition._links.config) : ''; @@ -78,7 +77,6 @@ export class SubmissionObjectEffects { sectionData = action.payload.item.metadata; } const sectionErrors = isNotEmpty(action.payload.errors) ? (action.payload.errors[sectionId] || null) : null; - mappedActions.push( new InitSectionAction( action.payload.submissionId, @@ -93,123 +91,7 @@ export class SubmissionObjectEffects { sectionErrors ) ); - - if (index === definition.sections.page.length - 1) { - mappedActions.push( - new InitSectionAction( - action.payload.submissionId, - 'sherpaPolicies', - 'submit.progressbar.sherpaPolicies', - 'submit.progressbar.sherpaPolicies', - true, - SectionsType.SherpaPolicies, - { main: null, other: 'READONLY' }, - true, - { - 'id': 'sherpaPolicies', - 'retrievalTime': '2022-04-20T09:44:39.870+00:00', - 'sherpaResponse': [ - { - 'error': false, - 'message': null, - 'metadata': { - 'id': 23803, - 'uri': 'http://v2.sherpa.ac.uk/id/publication/23803', - 'dateCreated': '2012-11-20 14:51:52', - 'dateModified': '2020-03-06 11:25:54', - 'inDOAJ': false, - 'publiclyVisible': true - }, - 'journals': [{ - 'titles': ['The Lancet', 'Lancet'], - 'url': 'http://www.thelancet.com/journals/lancet/issue/current', - 'issns': ['0140-6736', '1474-547X'], - 'romeoPub': 'Elsevier: The Lancet', - 'zetoPub': 'Elsevier: The Lancet', - 'publisher': { - 'name': 'Elsevier', - 'relationshipType': null, - 'country': null, - 'uri': 'http://www.elsevier.com/', - 'identifier': null, - 'publicationCount': 0, - 'paidAccessDescription': 'Open access', - 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' - }, - 'publishers': [{ - 'name': 'Elsevier', - 'relationshipType': null, - 'country': null, - 'uri': 'http://www.elsevier.com/', - 'identifier': null, - 'publicationCount': 0, - 'paidAccessDescription': 'Open access', - 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' - }], - 'policies': [{ - 'id': 0, - 'openAccessPermitted': false, - 'uri': null, - 'internalMoniker': 'Lancet', - 'permittedVersions': [{ - 'articleVersion': 'submitted', - 'option': 1, - 'conditions': ['Upon publication publisher copyright and source must be acknowledged', 'Upon publication must link to publisher version'], - 'prerequisites': [], - 'locations': ['Author\'s Homepage', 'Preprint Repository'], - 'licenses': [], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 1, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': [], - 'locations': ['Author\'s Homepage', 'Institutional Website'], - 'licenses': ['CC BY-NC-ND'], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 2, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': ['If Required by Funder'], - 'locations': ['Non-Commercial Repository'], - 'licenses': ['CC BY-NC-ND'], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 3, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': [], - 'locations': ['Non-Commercial Repository'], - 'licenses': [], - 'embargo': null - }], - 'urls': { - 'http://download.thelancet.com/flatcontentassets/authors/lancet-information-for-authors.pdf': 'Guidelines for Authors', - 'http://www.thelancet.com/journals/lancet/article/PIIS0140-6736%2813%2960720-5/fulltext': 'The Lancet journals welcome a new open access policy', - 'http://www.thelancet.com/lancet-information-for-authors/after-publication': 'What happens after publication?', - 'http://www.thelancet.com/lancet/information-for-authors/disclosure-of-results': 'Disclosure of results before publication', - 'https://www.elsevier.com/__data/assets/pdf_file/0005/78476/external-embargo-list.pdf': 'Journal Embargo Period List', - 'https://www.elsevier.com/__data/assets/pdf_file/0011/78473/UK-Embargo-Periods.pdf': 'Journal Embargo List for UK Authors' - }, - 'openAccessProhibited': false, - 'publicationCount': 0, - 'preArchiving': 'can', - 'postArchiving': 'can', - 'pubArchiving': 'cannot' - }], - 'inDOAJ': false - }] - } - ] - } as WorkspaceitemSectionSherpaPoliciesObject, - null - ) - ); - } - }); - console.log(mappedActions); return { action: action, definition: definition, mappedActions: mappedActions }; }), mergeMap((result) => { @@ -222,7 +104,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [InitSubmissionFormAction] */ - resetForm$ = createEffect(() => this.actions$.pipe( + resetForm$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.RESET_SUBMISSION_FORM), map((action: ResetSubmissionFormAction) => new InitSubmissionFormAction( @@ -238,40 +120,41 @@ export class SubmissionObjectEffects { /** * Dispatch a [SaveSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error */ - saveSubmission$ = createEffect(() => this.actions$.pipe( + saveSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM), switchMap((action: SaveSubmissionFormAction) => { return this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual, action.payload.isManual)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); }))); /** * Dispatch a [SaveForLaterSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error */ - saveForLaterSubmission$ = createEffect(() => this.actions$.pipe( + saveForLaterSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM), switchMap((action: SaveForLaterSubmissionFormAction) => { return this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); }))); /** * Call parseSaveResponse and dispatch actions */ - saveSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + saveSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS), withLatestFrom(this.store$), 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); + action.payload.submissionObject, action.payload.submissionId, currentState.forms, + action.payload.showNotifications, action.payload.showErrors); }), mergeMap((actions) => observableFrom(actions)))); @@ -279,19 +162,19 @@ export class SubmissionObjectEffects { * Call parseSaveResponse and dispatch actions. * Notification system is forced to be disabled. */ - saveSubmissionSectionSuccess$ = createEffect(() => this.actions$.pipe( + saveSubmissionSectionSuccess$ = createEffect(() => 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); + action.payload.submissionObject, action.payload.submissionId, currentState.forms, false, false); }), mergeMap((actions) => observableFrom(actions)))); /** * Dispatch a [SaveSubmissionSectionFormSuccessAction] or a [SaveSubmissionSectionFormErrorAction] on error */ - saveSection$ = createEffect(() => this.actions$.pipe( + saveSection$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM), switchMap((action: SaveSubmissionSectionFormAction) => { return this.operationsService.jsonPatchByResourceID( @@ -299,14 +182,14 @@ export class SubmissionObjectEffects { action.payload.submissionId, 'sections', action.payload.sectionId).pipe( - map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); }))); /** * Show a notification on error */ - saveError$ = createEffect(() => this.actions$.pipe( + saveError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR), withLatestFrom(this.store$), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.save_error_notice')))), { dispatch: false }); @@ -314,7 +197,7 @@ export class SubmissionObjectEffects { /** * Call parseSaveResponse and dispatch actions or dispatch [SaveSubmissionFormErrorAction] on error */ - saveAndDeposit$ = createEffect(() => this.actions$.pipe( + saveAndDeposit$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION), withLatestFrom(this.submissionService.hasUnsavedModification()), switchMap(([action, hasUnsavedModification]: [SaveAndDepositSubmissionAction, boolean]) => { @@ -335,7 +218,13 @@ export class SubmissionObjectEffects { if (this.canDeposit(response)) { return new DepositSubmissionAction(action.payload.submissionId); } else { - return new SaveSubmissionFormSuccessAction(action.payload.submissionId, response); + this.notificationsService.warning( + null, + this.translate.instant('submission.sections.general.cannot_deposit'), + null, + true + ); + return new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, false, true); } }), catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); @@ -344,7 +233,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [DepositSubmissionSuccessAction] or a [DepositSubmissionErrorAction] on error */ - depositSubmission$ = createEffect(() => this.actions$.pipe( + depositSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION), withLatestFrom(this.store$), switchMap(([action, state]: [DepositSubmissionAction, any]) => { @@ -356,7 +245,7 @@ export class SubmissionObjectEffects { /** * Show a notification on success and redirect to MyDSpace page */ - saveForLaterSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + saveForLaterSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())), { dispatch: false }); @@ -364,7 +253,7 @@ export class SubmissionObjectEffects { /** * Show a notification on success and redirect to MyDSpace page */ - depositSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + depositSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.deposit_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())), { dispatch: false }); @@ -372,14 +261,14 @@ export class SubmissionObjectEffects { /** * Show a notification on error */ - depositSubmissionError$ = createEffect(() => this.actions$.pipe( + depositSubmissionError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.deposit_error_notice')))), { dispatch: false }); /** * Dispatch a [DiscardSubmissionSuccessAction] or a [DiscardSubmissionErrorAction] on error */ - discardSubmission$ = createEffect(() => this.actions$.pipe( + discardSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION), switchMap((action: DepositSubmissionAction) => { return this.submissionService.discardSubmission(action.payload.submissionId).pipe( @@ -390,7 +279,7 @@ export class SubmissionObjectEffects { /** * Adds all metadata an item to the SubmissionForm sections of the submission */ - addAllMetadataToSectionData = createEffect(() => this.actions$.pipe( + addAllMetadataToSectionData = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.UPDATE_SECTION_DATA), switchMap((action: UpdateSectionDataAction) => { return this.sectionService.getSectionState(action.payload.submissionId, action.payload.sectionId, SectionsType.Upload) @@ -431,18 +320,18 @@ export class SubmissionObjectEffects { /** * Show a notification on error */ - discardSubmissionError$ = createEffect(() => this.actions$.pipe( + discardSubmissionError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice')))), { dispatch: false }); constructor(private actions$: Actions, - private notificationsService: NotificationsService, - private operationsService: SubmissionJsonPatchOperationsService, - private sectionService: SectionsService, - private store$: Store, - private submissionService: SubmissionService, - private submissionObjectService: SubmissionObjectDataService, - private translate: TranslateService) { + private notificationsService: NotificationsService, + private operationsService: SubmissionJsonPatchOperationsService, + private sectionService: SectionsService, + private store$: Store, + private submissionService: SubmissionService, + private submissionObjectService: SubmissionObjectDataService, + private translate: TranslateService) { } /** @@ -481,18 +370,23 @@ export class SubmissionObjectEffects { * A boolean that indicate if show notification or not * @return SubmissionObjectAction[] * List of SubmissionObjectAction to dispatch + * @param showNotifications + * A boolean representing if to show notifications on save + * @param showErrors + * A boolean representing if to show errors on save */ protected parseSaveResponse( currentState: SubmissionObjectEntry, response: SubmissionObject[], submissionId: string, forms: FormState, - notify: boolean = true): SubmissionObjectAction[] { + showNotifications: boolean = true, + showErrors: boolean = true): SubmissionObjectAction[] { const mappedActions = []; if (isNotEmpty(response)) { - if (notify) { + if (showNotifications) { this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice')); } @@ -504,7 +398,7 @@ export class SubmissionObjectEffects { if (errors && !isEmpty(errors)) { // to avoid dispatching an action for every error, create an array of errors per section errorsList = parseSectionErrors(errors); - if (notify) { + if (showNotifications) { this.notificationsService.warning(null, this.translate.get('submission.sections.general.sections_not_valid')); } } @@ -523,12 +417,12 @@ export class SubmissionObjectEffects { continue; } - if (notify && !currentState.sections[sectionId].enabled) { + if (showNotifications && !currentState.sections[sectionId].enabled) { this.submissionService.notifyNewSection(submissionId, sectionId, currentState.sections[sectionId].sectionType); } const sectionForm = getForm(forms, currentState, sectionId); - const filteredErrors = filterErrors(sectionForm, sectionErrors, currentState.sections[sectionId].sectionType, notify); + const filteredErrors = filterErrors(sectionForm, sectionErrors, currentState.sections[sectionId].sectionType, showErrors); mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, filteredErrors, sectionErrors)); } }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 3c2ad2505f..ec9bc5b3a0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3828,6 +3828,8 @@ "submission.sections.general.add-more": "Add more", + "submission.sections.general.cannot_deposit": "Deposit cannot be completed due to errors in the form.
Please fill out all required fields to complete the deposit.", + "submission.sections.general.collection": "Collection", "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.",