fix issue where the submission form wouldn't update after a relationship was added

This commit is contained in:
Art Lowel
2021-04-13 12:28:27 +02:00
parent 9c0c1b408c
commit a4a747e922
3 changed files with 124 additions and 62 deletions

View File

@@ -45,32 +45,32 @@ export const sendRequest = (requestService: RequestService) =>
(source: Observable<RestRequest>): Observable<RestRequest> => (source: Observable<RestRequest>): Observable<RestRequest> =>
source.pipe(tap((request: RestRequest) => requestService.send(request))); source.pipe(tap((request: RestRequest) => requestService.send(request)));
export const getRemoteDataPayload = () => export const getRemoteDataPayload = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<T> => (source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload)); source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload));
export const getPaginatedListPayload = () => export const getPaginatedListPayload = <T>() =>
<T>(source: Observable<PaginatedList<T>>): Observable<T[]> => (source: Observable<PaginatedList<T>>): Observable<T[]> =>
source.pipe(map((list: PaginatedList<T>) => list.page)); source.pipe(map((list: PaginatedList<T>) => list.page));
export const getAllCompletedRemoteData = () => export const getAllCompletedRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(filter((rd: RemoteData<T>) => hasValue(rd) && rd.hasCompleted)); source.pipe(filter((rd: RemoteData<T>) => hasValue(rd) && rd.hasCompleted));
export const getFirstCompletedRemoteData = () => export const getFirstCompletedRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(getAllCompletedRemoteData(), take(1)); source.pipe(getAllCompletedRemoteData(), take(1));
export const takeUntilCompletedRemoteData = () => export const takeUntilCompletedRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(takeWhile((rd: RemoteData<T>) => hasNoValue(rd) || rd.isLoading, true)); source.pipe(takeWhile((rd: RemoteData<T>) => hasNoValue(rd) || rd.isLoading, true));
export const getFirstSucceededRemoteData = () => export const getFirstSucceededRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(filter((rd: RemoteData<T>) => rd.hasSucceeded), take(1)); source.pipe(filter((rd: RemoteData<T>) => rd.hasSucceeded), take(1));
export const getFirstSucceededRemoteWithNotEmptyData = () => export const getFirstSucceededRemoteWithNotEmptyData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload))); source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
/** /**
@@ -83,8 +83,8 @@ export const getFirstSucceededRemoteWithNotEmptyData = () =>
* These operators were created as a first step in refactoring * These operators were created as a first step in refactoring
* out all the instances where this is used incorrectly. * out all the instances where this is used incorrectly.
*/ */
export const getFirstSucceededRemoteDataPayload = () => export const getFirstSucceededRemoteDataPayload = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<T> => (source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe( source.pipe(
getFirstSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload() getRemoteDataPayload()
@@ -100,8 +100,8 @@ export const getFirstSucceededRemoteDataPayload = () =>
* These operators were created as a first step in refactoring * These operators were created as a first step in refactoring
* out all the instances where this is used incorrectly. * out all the instances where this is used incorrectly.
*/ */
export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => export const getFirstSucceededRemoteDataWithNotEmptyPayload = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<T> => (source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe( source.pipe(
getFirstSucceededRemoteWithNotEmptyData(), getFirstSucceededRemoteWithNotEmptyData(),
getRemoteDataPayload() getRemoteDataPayload()
@@ -117,8 +117,8 @@ export const getFirstSucceededRemoteDataWithNotEmptyPayload = () =>
* These operators were created as a first step in refactoring * These operators were created as a first step in refactoring
* out all the instances where this is used incorrectly. * out all the instances where this is used incorrectly.
*/ */
export const getAllSucceededRemoteDataPayload = () => export const getAllSucceededRemoteDataPayload = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<T> => (source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe( source.pipe(
getAllSucceededRemoteData(), getAllSucceededRemoteData(),
getRemoteDataPayload() getRemoteDataPayload()
@@ -138,8 +138,8 @@ export const getAllSucceededRemoteDataPayload = () =>
* These operators were created as a first step in refactoring * These operators were created as a first step in refactoring
* out all the instances where this is used incorrectly. * out all the instances where this is used incorrectly.
*/ */
export const getFirstSucceededRemoteListPayload = () => export const getFirstSucceededRemoteListPayload = <T>() =>
<T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> => (source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
source.pipe( source.pipe(
getFirstSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
@@ -160,8 +160,8 @@ export const getFirstSucceededRemoteListPayload = () =>
* These operators were created as a first step in refactoring * These operators were created as a first step in refactoring
* out all the instances where this is used incorrectly. * out all the instances where this is used incorrectly.
*/ */
export const getAllSucceededRemoteListPayload = () => export const getAllSucceededRemoteListPayload = <T>() =>
<T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> => (source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
source.pipe( source.pipe(
getAllSucceededRemoteData(), getAllSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
@@ -174,8 +174,8 @@ export const getAllSucceededRemoteListPayload = () =>
* @param router The router used to navigate to a new page * @param router The router used to navigate to a new page
* @param authService Service to check if the user is authenticated * @param authService Service to check if the user is authenticated
*/ */
export const redirectOn4xx = (router: Router, authService: AuthService) => export const redirectOn4xx = <T>(router: Router, authService: AuthService) =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
observableCombineLatest(source, authService.isAuthenticated()).pipe( observableCombineLatest(source, authService.isAuthenticated()).pipe(
map(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => { map(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => {
if (rd.hasFailed) { if (rd.hasFailed) {
@@ -229,16 +229,16 @@ export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: s
return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams }); return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams });
})); }));
export const getFinishedRemoteData = () => export const getFinishedRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => !rd.isLoading)); source.pipe(find((rd: RemoteData<T>) => !rd.isLoading));
export const getAllSucceededRemoteData = () => export const getAllSucceededRemoteData = <T>() =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => (source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(filter((rd: RemoteData<T>) => rd.hasSucceeded)); source.pipe(filter((rd: RemoteData<T>) => rd.hasSucceeded));
export const toDSpaceObjectListRD = () => export const toDSpaceObjectListRD = <T extends DSpaceObject>() =>
<T extends DSpaceObject>(source: Observable<RemoteData<PaginatedList<SearchResult<T>>>>): Observable<RemoteData<PaginatedList<T>>> => (source: Observable<RemoteData<PaginatedList<SearchResult<T>>>>): Observable<RemoteData<PaginatedList<T>>> =>
source.pipe( source.pipe(
filter((rd: RemoteData<PaginatedList<SearchResult<T>>>) => rd.hasSucceeded), filter((rd: RemoteData<PaginatedList<SearchResult<T>>>) => rd.hasSucceeded),
map((rd: RemoteData<PaginatedList<SearchResult<T>>>) => { map((rd: RemoteData<PaginatedList<SearchResult<T>>>) => {

View File

@@ -2,11 +2,11 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators'; import { filter, switchMap, debounceTime } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model';
import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util';
import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model';
import { SubmissionService } from '../submission.service'; import { SubmissionService } from '../submission.service';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -14,6 +14,9 @@ import { SubmissionObject } from '../../core/submission/models/submission-object
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { getAllSucceededRemoteData } from '../../core/shared/operators';
import { ItemDataService } from '../../core/data/item-data.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
/** /**
* This component allows to edit an existing workspaceitem/workflowitem. * This component allows to edit an existing workspaceitem/workflowitem.
@@ -60,6 +63,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
* @type {Array} * @type {Array}
*/ */
private subs: Subscription[] = []; private subs: Subscription[] = [];
/**
* BehaviorSubject containing the self link to the item for this submission
* @private
*/
private itemLink$: BehaviorSubject<string> = new BehaviorSubject(undefined);
/**
* The item for this submission.
*/
public item: Item; public item: Item;
/** /**
@@ -69,6 +82,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
* @param {NotificationsService} notificationsService * @param {NotificationsService} notificationsService
* @param {ActivatedRoute} route * @param {ActivatedRoute} route
* @param {Router} router * @param {Router} router
* @param {ItemDataService} itemDataService
* @param {SubmissionService} submissionService * @param {SubmissionService} submissionService
* @param {TranslateService} translate * @param {TranslateService} translate
*/ */
@@ -76,6 +90,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private itemDataService: ItemDataService,
private submissionService: SubmissionService, private submissionService: SubmissionService,
private translate: TranslateService) { private translate: TranslateService) {
} }
@@ -84,32 +99,47 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
* Retrieve workspaceitem/workflowitem from server and initialize all instance variables * Retrieve workspaceitem/workflowitem from server and initialize all instance variables
*/ */
ngOnInit() { ngOnInit() {
this.subs.push(this.route.paramMap.pipe( this.subs.push(
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), this.route.paramMap.pipe(
// NOTE new submission is retrieved on the browser side only, so get null on server side rendering switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
filter((submissionObjectRD: RemoteData<SubmissionObject>) => isNotNull(submissionObjectRD)) // NOTE new submission is retrieved on the browser side only, so get null on server side rendering
).subscribe((submissionObjectRD: RemoteData<SubmissionObject>) => { filter((submissionObjectRD: RemoteData<SubmissionObject>) => isNotNull(submissionObjectRD))
if (submissionObjectRD.hasSucceeded) { ).subscribe((submissionObjectRD: RemoteData<SubmissionObject>) => {
if (isEmpty(submissionObjectRD.payload)) { if (submissionObjectRD.hasSucceeded) {
this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit')); if (isEmpty(submissionObjectRD.payload)) {
this.router.navigate(['/mydspace']); this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit'));
this.router.navigate(['/mydspace']);
} else {
this.submissionId = submissionObjectRD.payload.id.toString();
this.collectionId = (submissionObjectRD.payload.collection as Collection).id;
this.selfUrl = submissionObjectRD.payload._links.self.href;
this.sections = submissionObjectRD.payload.sections;
this.itemLink$.next(submissionObjectRD.payload._links.item.href);
this.item = submissionObjectRD.payload.item;
this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel);
}
} else { } else {
this.submissionId = submissionObjectRD.payload.id.toString(); if (submissionObjectRD.statusCode === 404) {
this.collectionId = (submissionObjectRD.payload.collection as Collection).id; // redirect to not found page
this.selfUrl = submissionObjectRD.payload._links.self.href; this.router.navigate(['/404'], { skipLocationChange: true });
this.sections = submissionObjectRD.payload.sections; }
this.item = submissionObjectRD.payload.item as Item; // TODO handle generic error
this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel);
this.changeDetectorRef.detectChanges();
} }
} else { }),
if (submissionObjectRD.statusCode === 404) { this.itemLink$.pipe(
// redirect to not found page isNotEmptyOperator(),
this.router.navigate(['/404'], { skipLocationChange: true }); switchMap((itemLink: string) =>
} this.itemDataService.findByHref(itemLink)
// TODO handle generic error ),
} getAllSucceededRemoteData(),
})); // Multiple sources can update the item in quick succession.
// We only want to rerender the form if the item is unchanged for some time
debounceTime(300),
).subscribe((itemRd: RemoteData<Item>) => {
this.item = itemRd.payload;
this.changeDetectorRef.detectChanges();
}),
);
} }
/** /**

View File

@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util';
import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -12,6 +12,11 @@ import { SubmissionObject } from '../../core/submission/models/submission-object
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { switchMap, debounceTime } from 'rxjs/operators';
import { getAllSucceededRemoteData } from '../../core/shared/operators';
import { RemoteData } from '../../core/data/remote-data';
import { ItemDataService } from '../../core/data/item-data.service';
/** /**
* This component allows to submit a new workspaceitem. * This component allows to submit a new workspaceitem.
@@ -28,6 +33,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
* @type {string} * @type {string}
*/ */
public collectionId: string; public collectionId: string;
/**
* BehaviorSubject containing the self link to the item for this submission
* @private
*/
private itemLink$: BehaviorSubject<string> = new BehaviorSubject(undefined);
/**
* The item for this submission.
*/
public item: Item; public item: Item;
/** /**
@@ -71,6 +86,7 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
* *
* @param {ChangeDetectorRef} changeDetectorRef * @param {ChangeDetectorRef} changeDetectorRef
* @param {NotificationsService} notificationsService * @param {NotificationsService} notificationsService
* @param {ItemDataService} itemDataService
* @param {SubmissionService} submissionService * @param {SubmissionService} submissionService
* @param {Router} router * @param {Router} router
* @param {TranslateService} translate * @param {TranslateService} translate
@@ -80,13 +96,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
constructor(private changeDetectorRef: ChangeDetectorRef, constructor(private changeDetectorRef: ChangeDetectorRef,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private router: Router, private router: Router,
private itemDataService: ItemDataService,
private submissionService: SubmissionService, private submissionService: SubmissionService,
private translate: TranslateService, private translate: TranslateService,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private route: ActivatedRoute) { private route: ActivatedRoute) {
this.route this.route
.queryParams .queryParams
.subscribe((params) => { this.collectionParam = (params.collection); }); .subscribe((params) => {
this.collectionParam = (params.collection);
});
} }
/** /**
@@ -108,11 +127,24 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
this.selfUrl = submissionObject._links.self.href; this.selfUrl = submissionObject._links.self.href;
this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel); this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel);
this.submissionId = submissionObject.id; this.submissionId = submissionObject.id;
this.itemLink$.next(submissionObject._links.item.href);
this.item = submissionObject.item as Item; this.item = submissionObject.item as Item;
this.changeDetectorRef.detectChanges();
} }
} }
}) }),
this.itemLink$.pipe(
isNotEmptyOperator(),
switchMap((itemLink: string) =>
this.itemDataService.findByHref(itemLink)
),
getAllSucceededRemoteData(),
// Multiple sources can update the item in quick succession.
// We only want to rerender the form if the item is unchanged for some time
debounceTime(300),
).subscribe((itemRd: RemoteData<Item>) => {
this.item = itemRd.payload;
this.changeDetectorRef.detectChanges();
})
); );
} }