Added more comments

This commit is contained in:
Giuseppe Digilio
2019-03-17 19:21:02 +01:00
parent d90f69d15e
commit 299c7f7e2c
6 changed files with 604 additions and 24 deletions

View File

@@ -24,7 +24,7 @@ import {
SaveSubmissionFormSuccessAction, SaveSubmissionFormSuccessAction,
SaveSubmissionSectionFormAction, SaveSubmissionSectionFormAction,
SaveSubmissionSectionFormErrorAction, SaveSubmissionSectionFormErrorAction,
SaveSubmissionSectionFormSuccessAction, SaveSubmissionSectionFormSuccessAction, SubmissionObjectAction,
SubmissionObjectActionTypes, SubmissionObjectActionTypes,
UpdateSectionDataAction UpdateSectionDataAction
} from './submission-objects.actions'; } from './submission-objects.actions';
@@ -48,6 +48,9 @@ import { SubmissionJsonPatchOperationsService } from '../../core/submission/subm
@Injectable() @Injectable()
export class SubmissionObjectEffects { export class SubmissionObjectEffects {
/**
* Dispatch a [InitSectionAction] for every submission sections and dispatch a [CompleteInitSubmissionFormAction]
*/
@Effect() loadForm$ = this.actions$.pipe( @Effect() loadForm$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.INIT_SUBMISSION_FORM), ofType(SubmissionObjectActionTypes.INIT_SUBMISSION_FORM),
map((action: InitSubmissionFormAction) => { map((action: InitSubmissionFormAction) => {
@@ -83,6 +86,9 @@ export class SubmissionObjectEffects {
)); ));
})); }));
/**
* Dispatch a [InitSubmissionFormAction]
*/
@Effect() resetForm$ = this.actions$.pipe( @Effect() resetForm$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.RESET_SUBMISSION_FORM), ofType(SubmissionObjectActionTypes.RESET_SUBMISSION_FORM),
map((action: ResetSubmissionFormAction) => map((action: ResetSubmissionFormAction) =>
@@ -95,6 +101,9 @@ export class SubmissionObjectEffects {
null null
))); )));
/**
* Dispatch a [SaveSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error
*/
@Effect() saveSubmission$ = this.actions$.pipe( @Effect() saveSubmission$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM), ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM),
switchMap((action: SaveSubmissionFormAction) => { switchMap((action: SaveSubmissionFormAction) => {
@@ -106,6 +115,9 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId))));
})); }));
/**
* Dispatch a [SaveForLaterSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error
*/
@Effect() saveForLaterSubmission$ = this.actions$.pipe( @Effect() saveForLaterSubmission$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM), ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM),
switchMap((action: SaveForLaterSubmissionFormAction) => { switchMap((action: SaveForLaterSubmissionFormAction) => {
@@ -117,6 +129,9 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId))));
})); }));
/**
* Call parseSaveResponse and dispatch actions
*/
@Effect() saveSubmissionSuccess$ = this.actions$.pipe( @Effect() saveSubmissionSuccess$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS), ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS),
withLatestFrom(this.store$), withLatestFrom(this.store$),
@@ -125,6 +140,9 @@ export class SubmissionObjectEffects {
}), }),
mergeMap((actions) => observableFrom(actions))); mergeMap((actions) => observableFrom(actions)));
/**
* Dispatch a [SaveSubmissionSectionFormSuccessAction] or a [SaveSubmissionSectionFormErrorAction] on error
*/
@Effect() saveSection$ = this.actions$.pipe( @Effect() saveSection$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM), ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM),
switchMap((action: SaveSubmissionSectionFormAction) => { switchMap((action: SaveSubmissionSectionFormAction) => {
@@ -137,11 +155,17 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId))));
})); }));
/**
* Show a notification on error
*/
@Effect({dispatch: false}) saveError$ = this.actions$.pipe( @Effect({dispatch: false}) saveError$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR), ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR),
withLatestFrom(this.store$), withLatestFrom(this.store$),
tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.save_error_notice')))); tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.save_error_notice'))));
/**
* Call parseSaveResponse and dispatch actions or dispatch [SaveSubmissionFormErrorAction] on error
*/
@Effect() saveAndDeposit$ = this.actions$.pipe( @Effect() saveAndDeposit$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION), ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION),
withLatestFrom(this.store$), withLatestFrom(this.store$),
@@ -161,6 +185,9 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId))));
})); }));
/**
* Dispatch a [DepositSubmissionSuccessAction] or a [DepositSubmissionErrorAction] on error
*/
@Effect() depositSubmission$ = this.actions$.pipe( @Effect() depositSubmission$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION), ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION),
withLatestFrom(this.store$), withLatestFrom(this.store$),
@@ -170,20 +197,32 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new DepositSubmissionErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new DepositSubmissionErrorAction(action.payload.submissionId))));
})); }));
/**
* Show a notification on success and redirect to MyDSpace page
*/
@Effect({dispatch: false}) saveForLaterSubmissionSuccess$ = this.actions$.pipe( @Effect({dispatch: false}) saveForLaterSubmissionSuccess$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS), ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS),
tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice'))), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice'))),
tap(() => this.submissionService.redirectToMyDSpace())); tap(() => this.submissionService.redirectToMyDSpace()));
/**
* Show a notification on success and redirect to MyDSpace page
*/
@Effect({dispatch: false}) depositSubmissionSuccess$ = this.actions$.pipe( @Effect({dispatch: false}) depositSubmissionSuccess$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_SUCCESS), ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_SUCCESS),
tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.deposit_success_notice'))), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.deposit_success_notice'))),
tap(() => this.submissionService.redirectToMyDSpace())); tap(() => this.submissionService.redirectToMyDSpace()));
/**
* Show a notification on error
*/
@Effect({dispatch: false}) depositSubmissionError$ = this.actions$.pipe( @Effect({dispatch: false}) depositSubmissionError$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_ERROR), ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_ERROR),
tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.deposit_error_notice')))); tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.deposit_error_notice'))));
/**
* Dispatch a [DiscardSubmissionSuccessAction] or a [DiscardSubmissionErrorAction] on error
*/
@Effect() discardSubmission$ = this.actions$.pipe( @Effect() discardSubmission$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION), ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION),
switchMap((action: DepositSubmissionAction) => { switchMap((action: DepositSubmissionAction) => {
@@ -192,11 +231,17 @@ export class SubmissionObjectEffects {
catchError(() => observableOf(new DiscardSubmissionErrorAction(action.payload.submissionId)))); catchError(() => observableOf(new DiscardSubmissionErrorAction(action.payload.submissionId))));
})); }));
/**
* Show a notification on success and redirect to MyDSpace page
*/
@Effect({dispatch: false}) discardSubmissionSuccess$ = this.actions$.pipe( @Effect({dispatch: false}) discardSubmissionSuccess$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_SUCCESS), ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_SUCCESS),
tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.discard_success_notice'))), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.discard_success_notice'))),
tap(() => this.submissionService.redirectToMyDSpace())); tap(() => this.submissionService.redirectToMyDSpace()));
/**
* Show a notification on error
*/
@Effect({dispatch: false}) discardSubmissionError$ = this.actions$.pipe( @Effect({dispatch: false}) discardSubmissionError$ = this.actions$.pipe(
ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR), ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR),
tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice')))); tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice'))));
@@ -210,6 +255,12 @@ export class SubmissionObjectEffects {
private translate: TranslateService) { private translate: TranslateService) {
} }
/**
* Check if the submission object retrieved from REST haven't section errors
*
* @param response
* The submission object retrieved from REST
*/
protected canDeposit(response: SubmissionObject[]) { protected canDeposit(response: SubmissionObject[]) {
let canDeposit = true; let canDeposit = true;
@@ -225,7 +276,26 @@ export class SubmissionObjectEffects {
return canDeposit; return canDeposit;
} }
protected parseSaveResponse(currentState: SubmissionObjectEntry, response: SubmissionObject[], submissionId: string, notify: boolean = true) { /**
* Parse the submission object retrieved from REST haven't section errors and return actions to dispatch
*
* @param currentState
* The current SubmissionObjectEntry
* @param response
* The submission object retrieved from REST
* @param submissionId
* The submission id
* @param notify
* A boolean that indicate if show notification or not
* @return SubmissionObjectAction[]
* List of SubmissionObjectAction to dispatch
*/
protected parseSaveResponse(
currentState: SubmissionObjectEntry,
response: SubmissionObject[],
submissionId: string,
notify: boolean = true): SubmissionObjectAction[] {
const mappedActions = []; const mappedActions = [];
if (isNotEmpty(response)) { if (isNotEmpty(response)) {

View File

@@ -38,42 +38,138 @@ import { WorkspaceitemSectionDataType } from '../../core/submission/models/works
import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model'; import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model';
import { SectionsType } from '../sections/sections-type'; import { SectionsType } from '../sections/sections-type';
/**
* An interface to represent section visibility
*/
export interface SectionVisibility { export interface SectionVisibility {
main: any; main: any;
other: any; other: any;
} }
/**
* An interface to represent section object state
*/
export interface SubmissionSectionObject { export interface SubmissionSectionObject {
/**
* The section header
*/
header: string; header: string;
/**
* The section configuration url
*/
config: string; config: string;
/**
* A boolean representing if this section is mandatory
*/
mandatory: boolean; mandatory: boolean;
/**
* The section type
*/
sectionType: SectionsType; sectionType: SectionsType;
/**
* The section visibility
*/
visibility: SectionVisibility; visibility: SectionVisibility;
/**
* A boolean representing if this section is collapsed
*/
collapsed: boolean, collapsed: boolean,
/**
* A boolean representing if this section is enabled
*/
enabled: boolean; enabled: boolean;
/**
* The section data object
*/
data: WorkspaceitemSectionDataType; data: WorkspaceitemSectionDataType;
/**
* The list of the section errors
*/
errors: SubmissionSectionError[]; errors: SubmissionSectionError[];
/**
* A boolean representing if this section is loading
*/
isLoading: boolean; isLoading: boolean;
/**
* A boolean representing if this section is valid
*/
isValid: boolean; isValid: boolean;
} }
/**
* An interface to represent section error
*/
export interface SubmissionSectionError { export interface SubmissionSectionError {
/**
* A string representing error path
*/
path: string; path: string;
/**
* The error message
*/
message: string; message: string;
} }
/**
* An interface to represent SubmissionSectionObject entry
*/
export interface SubmissionSectionEntry { export interface SubmissionSectionEntry {
[sectionId: string]: SubmissionSectionObject; [sectionId: string]: SubmissionSectionObject;
} }
/**
* An interface to represent submission object state
*/
export interface SubmissionObjectEntry { export interface SubmissionObjectEntry {
/**
* The collection this submission belonging to
*/
collection?: string, collection?: string,
/**
* The configuration name tha define this submission
*/
definition?: string, definition?: string,
/**
* The submission self url
*/
selfUrl?: string; selfUrl?: string;
/**
* The submission active section
*/
activeSection?: string; activeSection?: string;
/**
* The list of submission's sections
*/
sections?: SubmissionSectionEntry; sections?: SubmissionSectionEntry;
/**
* A boolean representing if this submission is loading
*/
isLoading?: boolean; isLoading?: boolean;
/**
* A boolean representing if a submission save operation is pending
*/
savePending?: boolean; savePending?: boolean;
/**
* A boolean representing if a submission deposit operation is pending
*/
depositPending?: boolean; depositPending?: boolean;
} }

View File

@@ -35,9 +35,20 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { SubmissionService } from '../submission.service'; import { SubmissionService } from '../submission.service';
import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model';
/**
* A service that provides methods used in submission process.
*/
@Injectable() @Injectable()
export class SectionsService { export class SectionsService {
/**
* Initialize service variables
* @param {NotificationsService} notificationsService
* @param {ScrollToService} scrollToService
* @param {SubmissionService} submissionService
* @param {Store<SubmissionState>} store
* @param {TranslateService} translate
*/
constructor(private notificationsService: NotificationsService, constructor(private notificationsService: NotificationsService,
private scrollToService: ScrollToService, private scrollToService: ScrollToService,
private submissionService: SubmissionService, private submissionService: SubmissionService,
@@ -45,17 +56,35 @@ export class SectionsService {
private translate: TranslateService) { private translate: TranslateService) {
} }
/**
* Compare the list of the current section errors with the previous one,
* and dispatch actions to add/remove to/from the section state
*
* @param submissionId
* The submission id
* @param sectionId
* The workspaceitem self url
* @param formId
* The [SubmissionDefinitionsModel] that define submission configuration
* @param currentErrors
* The [SubmissionSectionError] that define submission sections init data
* @param prevErrors
* The [SubmissionSectionError] that define submission sections init errors
*/
public checkSectionErrors( public checkSectionErrors(
submissionId: string, submissionId: string,
sectionId: string, sectionId: string,
formId: string, formId: string,
currentErrors: SubmissionSectionError[], currentErrors: SubmissionSectionError[],
prevErrors: SubmissionSectionError[] = []) { prevErrors: SubmissionSectionError[] = []) {
// Remove previous error list if the current is empty
if (isEmpty(currentErrors)) { if (isEmpty(currentErrors)) {
this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId)); this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId));
this.store.dispatch(new FormClearErrorsAction(formId)); this.store.dispatch(new FormClearErrorsAction(formId));
} else if (!isEqual(currentErrors, prevErrors)) { } else if (!isEqual(currentErrors, prevErrors)) { // compare previous error list with the current one
const dispatchedErrors = []; const dispatchedErrors = [];
// Itereate over the current error list
currentErrors.forEach((error: SubmissionSectionError) => { currentErrors.forEach((error: SubmissionSectionError) => {
const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path); const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path);
@@ -63,7 +92,7 @@ export class SectionsService {
if (path.fieldId) { if (path.fieldId) {
const fieldId = path.fieldId.replace(/\./g, '_'); const fieldId = path.fieldId.replace(/\./g, '_');
// Dispatch action to the form state; // Dispatch action to add form error to the state;
const formAddErrorAction = new FormAddError(formId, fieldId, path.fieldIndex, error.message); const formAddErrorAction = new FormAddError(formId, fieldId, path.fieldIndex, error.message);
this.store.dispatch(formAddErrorAction); this.store.dispatch(formAddErrorAction);
dispatchedErrors.push(fieldId); dispatchedErrors.push(fieldId);
@@ -71,6 +100,7 @@ export class SectionsService {
}); });
}); });
// Itereate over the previous error list
prevErrors.forEach((error: SubmissionSectionError) => { prevErrors.forEach((error: SubmissionSectionError) => {
const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path); const errorPaths: SectionErrorPath[] = parseSectionErrorPaths(error.path);
@@ -79,6 +109,7 @@ export class SectionsService {
const fieldId = path.fieldId.replace(/\./g, '_'); const fieldId = path.fieldId.replace(/\./g, '_');
if (!dispatchedErrors.includes(fieldId)) { if (!dispatchedErrors.includes(fieldId)) {
// Dispatch action to remove form error from the state;
const formRemoveErrorAction = new FormRemoveErrorAction(formId, fieldId, path.fieldIndex); const formRemoveErrorAction = new FormRemoveErrorAction(formId, fieldId, path.fieldIndex);
this.store.dispatch(formRemoveErrorAction); this.store.dispatch(formRemoveErrorAction);
} }
@@ -88,20 +119,58 @@ export class SectionsService {
} }
} }
/**
* Dispatch a new [RemoveSectionErrorsAction]
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
*/
public dispatchRemoveSectionErrors(submissionId, sectionId) { public dispatchRemoveSectionErrors(submissionId, sectionId) {
this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId)); this.store.dispatch(new RemoveSectionErrorsAction(submissionId, sectionId));
} }
/**
* Return the data object for the specified section
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<WorkspaceitemSectionDataType>
* observable of [WorkspaceitemSectionDataType]
*/
public getSectionData(submissionId: string, sectionId: string): Observable<WorkspaceitemSectionDataType> { public getSectionData(submissionId: string, sectionId: string): Observable<WorkspaceitemSectionDataType> {
return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe(
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Return the error list object data for the specified section
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<SubmissionSectionError>
* observable of array of [SubmissionSectionError]
*/
public getSectionErrors(submissionId: string, sectionId: string): Observable<SubmissionSectionError[]> { public getSectionErrors(submissionId: string, sectionId: string): Observable<SubmissionSectionError[]> {
return this.store.select(submissionSectionErrorsFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionErrorsFromIdSelector(submissionId, sectionId)).pipe(
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Return the state object for the specified section
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<SubmissionSectionObject>
* observable of [SubmissionSectionObject]
*/
public getSectionState(submissionId: string, sectionId: string): Observable<SubmissionSectionObject> { public getSectionState(submissionId: string, sectionId: string): Observable<SubmissionSectionObject> {
return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe(
filter((sectionObj: SubmissionSectionObject) => hasValue(sectionObj)), filter((sectionObj: SubmissionSectionObject) => hasValue(sectionObj)),
@@ -109,6 +178,16 @@ export class SectionsService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Check if a given section is valid
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<boolean>
* Emits true whenever a given section should be valid
*/
public isSectionValid(submissionId: string, sectionId: string): Observable<boolean> { public isSectionValid(submissionId: string, sectionId: string): Observable<boolean> {
return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe(
filter((sectionObj) => hasValue(sectionObj)), filter((sectionObj) => hasValue(sectionObj)),
@@ -116,12 +195,32 @@ export class SectionsService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Check if a given section is active
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<boolean>
* Emits true whenever a given section should be active
*/
public isSectionActive(submissionId: string, sectionId: string): Observable<boolean> { public isSectionActive(submissionId: string, sectionId: string): Observable<boolean> {
return this.submissionService.getActiveSectionId(submissionId).pipe( return this.submissionService.getActiveSectionId(submissionId).pipe(
map((activeSectionId: string) => sectionId === activeSectionId), map((activeSectionId: string) => sectionId === activeSectionId),
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Check if a given section is enabled
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<boolean>
* Emits true whenever a given section should be enabled
*/
public isSectionEnabled(submissionId: string, sectionId: string): Observable<boolean> { public isSectionEnabled(submissionId: string, sectionId: string): Observable<boolean> {
return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe(
filter((sectionObj) => hasValue(sectionObj)), filter((sectionObj) => hasValue(sectionObj)),
@@ -129,6 +228,18 @@ export class SectionsService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Check if a given section is a read only section
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param submissionScope
* The submission scope
* @return Observable<boolean>
* Emits true whenever a given section should be read only
*/
public isSectionReadOnly(submissionId: string, sectionId: string, submissionScope: SubmissionScopeType): Observable<boolean> { public isSectionReadOnly(submissionId: string, sectionId: string, submissionScope: SubmissionScopeType): Observable<boolean> {
return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionSectionFromIdSelector(submissionId, sectionId)).pipe(
filter((sectionObj) => hasValue(sectionObj)), filter((sectionObj) => hasValue(sectionObj)),
@@ -140,6 +251,16 @@ export class SectionsService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Check if a given section is a read only available
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @return Observable<boolean>
* Emits true whenever a given section should be available
*/
public isSectionAvailable(submissionId: string, sectionId: string): Observable<boolean> { public isSectionAvailable(submissionId: string, sectionId: string): Observable<boolean> {
return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe(
filter((submissionState: SubmissionObjectEntry) => isNotUndefined(submissionState)), filter((submissionState: SubmissionObjectEntry) => isNotUndefined(submissionState)),
@@ -149,8 +270,15 @@ export class SectionsService {
distinctUntilChanged()); distinctUntilChanged());
} }
public addSection(submissionId: string, /**
sectionId: string) { * Dispatch a new [EnableSectionAction] to add a new section and move page target to it
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
*/
public addSection(submissionId: string, sectionId: string) {
this.store.dispatch(new EnableSectionAction(submissionId, sectionId)); this.store.dispatch(new EnableSectionAction(submissionId, sectionId));
const config: ScrollToConfigOptions = { const config: ScrollToConfigOptions = {
target: sectionId, target: sectionId,
@@ -160,11 +288,31 @@ export class SectionsService {
this.scrollToService.scrollTo(config); this.scrollToService.scrollTo(config);
} }
/**
* Dispatch a new [DisableSectionAction] to remove section
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
*/
public removeSection(submissionId: string, sectionId: string) { public removeSection(submissionId: string, sectionId: string) {
this.store.dispatch(new DisableSectionAction(submissionId, sectionId)) this.store.dispatch(new DisableSectionAction(submissionId, sectionId))
} }
public updateSectionData(submissionId: string, sectionId: string, data, errors = []) { /**
* Dispatch a new [UpdateSectionDataAction] to update section state with new data and errors
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param data
* The section data
* @param errors
* The list of section errors
*/
public updateSectionData(submissionId: string, sectionId: string, data: WorkspaceitemSectionDataType, errors: SubmissionSectionError[] = []) {
if (isNotEmpty(data)) { if (isNotEmpty(data)) {
const isAvailable$ = this.isSectionAvailable(submissionId, sectionId); const isAvailable$ = this.isSectionAvailable(submissionId, sectionId);
const isEnabled$ = this.isSectionEnabled(submissionId, sectionId); const isEnabled$ = this.isSectionEnabled(submissionId, sectionId);
@@ -182,10 +330,30 @@ export class SectionsService {
} }
} }
/**
* Dispatch a new [InertSectionErrorsAction] to update section state with new error
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param error
* The section error
*/
public setSectionError(submissionId: string, sectionId: string, error: SubmissionSectionError) { public setSectionError(submissionId: string, sectionId: string, error: SubmissionSectionError) {
this.store.dispatch(new InertSectionErrorsAction(submissionId, sectionId, error)); this.store.dispatch(new InertSectionErrorsAction(submissionId, sectionId, error));
} }
/**
* Dispatch a new [SectionStatusChangeAction] to update section state with new status
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param status
* The section status
*/
public setSectionStatus(submissionId: string, sectionId: string, status: boolean) { public setSectionStatus(submissionId: string, sectionId: string, status: boolean) {
this.store.dispatch(new SectionStatusChangeAction(submissionId, sectionId, status)); this.store.dispatch(new SectionStatusChangeAction(submissionId, sectionId, status));
} }

View File

@@ -777,7 +777,7 @@ describe('SubmissionService test suite', () => {
service.notifyNewSection(submissionId, sectionId); service.notifyNewSection(submissionId, sectionId);
flush(); flush();
expect((service as any).notificationsService.info).toHaveBeenCalledWith(null, 'test', null, true); expect((service as any).notificationsService.info).toHaveBeenCalledWith(null, 'submission.sections.general.metadata-extracted-new-section', null, true);
})); }));
}); });
@@ -890,7 +890,7 @@ describe('SubmissionService test suite', () => {
const duration = config.submission.autosave.timer * (1000 * 60); const duration = config.submission.autosave.timer * (1000 * 60);
service.startAutoSave('826'); service.startAutoSave('826');
const sub = (service as any).timerObs.subscribe(); const sub = (service as any).timer$.subscribe();
tick(duration / 2); tick(duration / 2);
expect((service as any).store.dispatch).not.toHaveBeenCalled(); expect((service as any).store.dispatch).not.toHaveBeenCalled();

View File

@@ -44,12 +44,32 @@ import { RemoteData } from '../core/data/remote-data';
import { ErrorResponse } from '../core/cache/response.models'; import { ErrorResponse } from '../core/cache/response.models';
import { RemoteDataError } from '../core/data/remote-data-error'; import { RemoteDataError } from '../core/data/remote-data-error';
/**
* A service that provides methods used in submission process.
*/
@Injectable() @Injectable()
export class SubmissionService { export class SubmissionService {
/**
* Subscription
*/
protected autoSaveSub: Subscription; protected autoSaveSub: Subscription;
protected timerObs: Observable<any>;
/**
* Observable used as timer
*/
protected timer$: Observable<any>;
/**
* Initialize service variables
* @param {GlobalConfig} EnvConfig
* @param {NotificationsService} notificationsService
* @param {SubmissionRestService} restService
* @param {Router} restSerroutervice
* @param {RouteService} routeService
* @param {Store<SubmissionState>} store
* @param {TranslateService} translate
*/
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected restService: SubmissionRestService, protected restService: SubmissionRestService,
@@ -59,28 +79,74 @@ export class SubmissionService {
protected translate: TranslateService) { protected translate: TranslateService) {
} }
/**
* Dispatch a new [ChangeSubmissionCollectionAction]
*
* @param submissionId
* The submission id
* @param collectionId
* The collection id
*/
changeSubmissionCollection(submissionId, collectionId) { changeSubmissionCollection(submissionId, collectionId) {
this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId)); this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId));
} }
/**
* Perform a REST call to create a new workspaceitem and return response
*
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
createSubmission(): Observable<SubmissionObject> { createSubmission(): Observable<SubmissionObject> {
return this.restService.postToEndpoint('workspaceitems', {}).pipe( return this.restService.postToEndpoint('workspaceitems', {}).pipe(
map((workspaceitem: SubmissionObject) => workspaceitem[0]), map((workspaceitem: SubmissionObject) => workspaceitem[0]),
catchError(() => observableOf({}))) catchError(() => observableOf({})))
} }
depositSubmission(selfUrl: string): Observable<any> { /**
* Perform a REST call to deposit a workspaceitem and return response
*
* @param selfUrl
* The workspaceitem self url
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
depositSubmission(selfUrl: string): Observable<SubmissionObject[]> {
const options: HttpOptions = Object.create({}); const options: HttpOptions = Object.create({});
let headers = new HttpHeaders(); let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'text/uri-list'); headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers; options.headers = headers;
return this.restService.postToEndpoint('workflowitems', selfUrl, null, options); return this.restService.postToEndpoint('workflowitems', selfUrl, null, options) as Observable<SubmissionObject[]>;
} }
discardSubmission(submissionId: string): Observable<any> { /**
return this.restService.deleteById(submissionId); * Perform a REST call to delete a workspaceitem and return response
*
* @param submissionId
* The submission id
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
discardSubmission(submissionId: string): Observable<SubmissionObject[]> {
return this.restService.deleteById(submissionId) as Observable<SubmissionObject[]>;
} }
/**
* Dispatch a new [InitSubmissionFormAction]
*
* @param collectionId
* The collection id
* @param submissionId
* The submission id
* @param selfUrl
* The workspaceitem self url
* @param submissionDefinition
* The [SubmissionDefinitionsModel] that define submission configuration
* @param sections
* The [WorkspaceitemSectionsObject] that define submission sections init data
* @param errors
* The [SubmissionSectionError] that define submission sections init errors
*/
dispatchInit( dispatchInit(
collectionId: string, collectionId: string,
submissionId: string, submissionId: string,
@@ -91,36 +157,90 @@ export class SubmissionService {
this.store.dispatch(new InitSubmissionFormAction(collectionId, submissionId, selfUrl, submissionDefinition, sections, errors)); this.store.dispatch(new InitSubmissionFormAction(collectionId, submissionId, selfUrl, submissionDefinition, sections, errors));
} }
/**
* Dispatch a new [SaveAndDepositSubmissionAction]
*
* @param submissionId
* The submission id
*/
dispatchDeposit(submissionId) { dispatchDeposit(submissionId) {
this.store.dispatch(new SaveAndDepositSubmissionAction(submissionId)); this.store.dispatch(new SaveAndDepositSubmissionAction(submissionId));
} }
/**
* Dispatch a new [DiscardSubmissionAction]
*
* @param submissionId
* The submission id
*/
dispatchDiscard(submissionId) { dispatchDiscard(submissionId) {
this.store.dispatch(new DiscardSubmissionAction(submissionId)); this.store.dispatch(new DiscardSubmissionAction(submissionId));
} }
/**
* Dispatch a new [SaveSubmissionFormAction]
*
* @param submissionId
* The submission id
*/
dispatchSave(submissionId) { dispatchSave(submissionId) {
this.store.dispatch(new SaveSubmissionFormAction(submissionId)); this.store.dispatch(new SaveSubmissionFormAction(submissionId));
} }
/**
* Dispatch a new [SaveForLaterSubmissionFormAction]
*
* @param submissionId
* The submission id
*/
dispatchSaveForLater(submissionId) { dispatchSaveForLater(submissionId) {
this.store.dispatch(new SaveForLaterSubmissionFormAction(submissionId)); this.store.dispatch(new SaveForLaterSubmissionFormAction(submissionId));
} }
/**
* Dispatch a new [SaveSubmissionSectionFormAction]
*
* @param submissionId
* The submission id
*/
dispatchSaveSection(submissionId, sectionId) { dispatchSaveSection(submissionId, sectionId) {
this.store.dispatch(new SaveSubmissionSectionFormAction(submissionId, sectionId)); this.store.dispatch(new SaveSubmissionSectionFormAction(submissionId, sectionId));
} }
/**
* Return the id of the current focused section for the specified submission
*
* @param submissionId
* The submission id
* @return Observable<string>
* observable of section id
*/
getActiveSectionId(submissionId: string): Observable<string> { getActiveSectionId(submissionId: string): Observable<string> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
map((submission: SubmissionObjectEntry) => submission.activeSection)); map((submission: SubmissionObjectEntry) => submission.activeSection));
} }
/**
* Return the [SubmissionObjectEntry] for the specified submission
*
* @param submissionId
* The submission id
* @return Observable<SubmissionObjectEntry>
* observable of SubmissionObjectEntry
*/
getSubmissionObject(submissionId: string): Observable<SubmissionObjectEntry> { getSubmissionObject(submissionId: string): Observable<SubmissionObjectEntry> {
return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe( return this.store.select(submissionObjectFromIdSelector(submissionId)).pipe(
filter((submission: SubmissionObjectEntry) => isNotUndefined(submission))); filter((submission: SubmissionObjectEntry) => isNotUndefined(submission)));
} }
/**
* Return a list of the active [SectionDataObject] belonging to the specified submission
*
* @param submissionId
* The submission id
* @return Observable<SubmissionObjectEntry>
* observable with the list of active submission's sections
*/
getSubmissionSections(submissionId: string): Observable<SectionDataObject[]> { getSubmissionSections(submissionId: string): Observable<SectionDataObject[]> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
find((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading), find((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading),
@@ -146,6 +266,14 @@ export class SubmissionService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Return a list of the disabled [SectionDataObject] belonging to the specified submission
*
* @param submissionId
* The submission id
* @return Observable<SubmissionObjectEntry>
* observable with the list of disabled submission's sections
*/
getDisabledSectionsList(submissionId: string): Observable<SectionDataObject[]> { getDisabledSectionsList(submissionId: string): Observable<SectionDataObject[]> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
filter((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading), filter((submission: SubmissionObjectEntry) => isNotUndefined(submission.sections) && !submission.isLoading),
@@ -167,6 +295,12 @@ export class SubmissionService {
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Return the correct REST endpoint link path depending on the page route
*
* @return string
* link path
*/
getSubmissionObjectLinkName(): string { getSubmissionObjectLinkName(): string {
const url = this.router.routerState.snapshot.url; const url = this.router.routerState.snapshot.url;
if (url.startsWith('/workspaceitems') || url.startsWith('/submit')) { if (url.startsWith('/workspaceitems') || url.startsWith('/submit')) {
@@ -178,6 +312,12 @@ export class SubmissionService {
} }
} }
/**
* Return the submission scope
*
* @return SubmissionScopeType
* the SubmissionScopeType
*/
getSubmissionScope(): SubmissionScopeType { getSubmissionScope(): SubmissionScopeType {
let scope: SubmissionScopeType; let scope: SubmissionScopeType;
switch (this.getSubmissionObjectLinkName()) { switch (this.getSubmissionObjectLinkName()) {
@@ -194,6 +334,14 @@ export class SubmissionService {
return scope; return scope;
} }
/**
* Return the validity status of the submission
*
* @param submissionId
* The submission id
* @return Observable<boolean>
* observable with submission validity status
*/
getSubmissionStatus(submissionId: string): Observable<boolean> { getSubmissionStatus(submissionId: string): Observable<boolean> {
return this.store.select(submissionSelector).pipe( return this.store.select(submissionSelector).pipe(
map((submissions: SubmissionState) => submissions.objects[submissionId]), map((submissions: SubmissionState) => submissions.objects[submissionId]),
@@ -219,6 +367,14 @@ export class SubmissionService {
startWith(false)); startWith(false));
} }
/**
* Return the save processing status of the submission
*
* @param submissionId
* The submission id
* @return Observable<boolean>
* observable with submission save processing status
*/
getSubmissionSaveProcessingStatus(submissionId: string): Observable<boolean> { getSubmissionSaveProcessingStatus(submissionId: string): Observable<boolean> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
map((state: SubmissionObjectEntry) => state.savePending), map((state: SubmissionObjectEntry) => state.savePending),
@@ -226,6 +382,14 @@ export class SubmissionService {
startWith(false)); startWith(false));
} }
/**
* Return the deposit processing status of the submission
*
* @param submissionId
* The submission id
* @return Observable<boolean>
* observable with submission deposit processing status
*/
getSubmissionDepositProcessingStatus(submissionId: string): Observable<boolean> { getSubmissionDepositProcessingStatus(submissionId: string): Observable<boolean> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
map((state: SubmissionObjectEntry) => state.depositPending), map((state: SubmissionObjectEntry) => state.depositPending),
@@ -233,27 +397,50 @@ export class SubmissionService {
startWith(false)); startWith(false));
} }
isSectionHidden(sectionData: SubmissionSectionObject) { /**
* Return the visibility status of the specified section
*
* @param submissionId
* The submission id
* @return boolean
* true if section is hidden, false otherwise
*/
isSectionHidden(sectionData: SubmissionSectionObject): boolean {
return (isNotUndefined(sectionData.visibility) return (isNotUndefined(sectionData.visibility)
&& sectionData.visibility.main === 'HIDDEN' && sectionData.visibility.main === 'HIDDEN'
&& sectionData.visibility.other === 'HIDDEN'); && sectionData.visibility.other === 'HIDDEN');
} }
/**
* Return the loading status of the submission
*
* @param submissionId
* The submission id
* @return Observable<boolean>
* observable with submission loading status
*/
isSubmissionLoading(submissionId: string): Observable<boolean> { isSubmissionLoading(submissionId: string): Observable<boolean> {
return this.getSubmissionObject(submissionId).pipe( return this.getSubmissionObject(submissionId).pipe(
map((submission: SubmissionObjectEntry) => submission.isLoading), map((submission: SubmissionObjectEntry) => submission.isLoading),
distinctUntilChanged()); distinctUntilChanged());
} }
/**
* Show a notification when a new section is added to submission form
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
*/
notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) { notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) {
this.translate.get('submission.sections.general.metadata-extracted-new-section', { sectionId }).pipe( const m = this.translate.instant('submission.sections.general.metadata-extracted-new-section', { sectionId });
first()) this.notificationsService.info(null, m, null, true);
.subscribe((m) => {
this.notificationsService.info(null, m, null, true);
});
} }
/**
* Redirect to MyDspace page
*/
redirectToMyDSpace() { redirectToMyDSpace() {
this.routeService.getPreviousUrl().pipe( this.routeService.getPreviousUrl().pipe(
first() first()
@@ -267,10 +454,27 @@ export class SubmissionService {
} }
/**
* Dispatch a new [CancelSubmissionFormAction]
*/
resetAllSubmissionObjects() { resetAllSubmissionObjects() {
this.store.dispatch(new CancelSubmissionFormAction()); this.store.dispatch(new CancelSubmissionFormAction());
} }
/**
* Dispatch a new [ResetSubmissionFormAction]
*
* @param collectionId
* The collection id
* @param submissionId
* The submission id
* @param selfUrl
* The workspaceitem self url
* @param submissionDefinition
* The [SubmissionDefinitionsModel] that define submission configuration
* @param sections
* The [WorkspaceitemSectionsObject] that define submission sections init data
*/
resetSubmissionObject( resetSubmissionObject(
collectionId: string, collectionId: string,
submissionId: string, submissionId: string,
@@ -281,6 +485,12 @@ export class SubmissionService {
this.store.dispatch(new ResetSubmissionFormAction(collectionId, submissionId, selfUrl, sections, submissionDefinition)); this.store.dispatch(new ResetSubmissionFormAction(collectionId, submissionId, selfUrl, sections, submissionDefinition));
} }
/**
* Perform a REST call to retrieve an existing workspaceitem/workflowitem and return response
*
* @return Observable<RemoteData<SubmissionObject>>
* observable of RemoteData<SubmissionObject>
*/
retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> { retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> {
return this.restService.getDataById(this.getSubmissionObjectLinkName(), submissionId).pipe( return this.restService.getDataById(this.getSubmissionObjectLinkName(), submissionId).pipe(
find((submissionObjects: SubmissionObject[]) => isNotUndefined(submissionObjects)), find((submissionObjects: SubmissionObject[]) => isNotUndefined(submissionObjects)),
@@ -302,21 +512,38 @@ export class SubmissionService {
); );
} }
/**
* Dispatch a new [SetActiveSectionAction]
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
*/
setActiveSection(submissionId, sectionId) { setActiveSection(submissionId, sectionId) {
this.store.dispatch(new SetActiveSectionAction(submissionId, sectionId)); this.store.dispatch(new SetActiveSectionAction(submissionId, sectionId));
} }
/**
* Allow to save automatically the submission
*
* @param submissionId
* The submission id
*/
startAutoSave(submissionId) { startAutoSave(submissionId) {
this.stopAutoSave(); this.stopAutoSave();
// AUTOSAVE submission // AUTOSAVE submission
// Retrieve interval from config and convert to milliseconds // Retrieve interval from config and convert to milliseconds
const duration = this.EnvConfig.submission.autosave.timer * (1000 * 60); const duration = this.EnvConfig.submission.autosave.timer * (1000 * 60);
// Dispatch save action after given duration // Dispatch save action after given duration
this.timerObs = observableTimer(duration, duration); this.timer$ = observableTimer(duration, duration);
this.autoSaveSub = this.timerObs this.autoSaveSub = this.timer$
.subscribe(() => this.store.dispatch(new SaveSubmissionFormAction(submissionId))); .subscribe(() => this.store.dispatch(new SaveSubmissionFormAction(submissionId)));
} }
/**
* Unsubscribe subscription to timer
*/
stopAutoSave() { stopAutoSave() {
if (hasValue(this.autoSaveSub)) { if (hasValue(this.autoSaveSub)) {
this.autoSaveSub.unsubscribe(); this.autoSaveSub.unsubscribe();

View File

@@ -1,9 +1,28 @@
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
/**
* An interface to represent the path of a section error
*/
export interface SectionErrorPath { export interface SectionErrorPath {
/**
* The section id
*/
sectionId: string; sectionId: string;
/**
* The form field id
*/
fieldId?: string; fieldId?: string;
/**
* The form field index
*/
fieldIndex?: number; fieldIndex?: number;
/**
* The complete path
*/
originalPath: string; originalPath: string;
} }
@@ -12,7 +31,7 @@ const regex = /([^\/]+)/g;
const regexShort = /\/sections\/(.*)/; const regexShort = /\/sections\/(.*)/;
/** /**
* the following method accept an array of section path strings and return a path object * The following method accept an array of section path strings and return a path object
* @param {string | string[]} path * @param {string | string[]} path
* @returns {SectionErrorPath[]} * @returns {SectionErrorPath[]}
*/ */