mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
65717: Immediate patch request + delete in order; refresh cache and show notifications after submit
This commit is contained in:
@@ -296,11 +296,14 @@
|
||||
"item.edit.bitstreams.headers.name": "Name",
|
||||
"item.edit.bitstreams.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||
"item.edit.bitstreams.notifications.discarded.title": "Changes discarded",
|
||||
"item.edit.bitstreams.notifications.failed.title": "Error deleting bitstream",
|
||||
"item.edit.bitstreams.notifications.move.failed.title": "Error moving bitstreams",
|
||||
"item.edit.bitstreams.notifications.move.saved.content": "Your move changes to this item's bitstreams and bundles have been saved.",
|
||||
"item.edit.bitstreams.notifications.move.saved.title": "Move changes saved",
|
||||
"item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
||||
"item.edit.bitstreams.notifications.outdated.title": "Changes outdated",
|
||||
"item.edit.bitstreams.notifications.saved.content": "Your changes to this item's bitstreams were saved.",
|
||||
"item.edit.bitstreams.notifications.saved.title": "Changes saved",
|
||||
"item.edit.bitstreams.notifications.remove.failed.title": "Error deleting bitstream",
|
||||
"item.edit.bitstreams.notifications.remove.saved.content": "Your removal changes to this item's bitstreams have been saved.",
|
||||
"item.edit.bitstreams.notifications.remove.saved.title": "Removal changes saved",
|
||||
"item.edit.bitstreams.reinstate-button": "Undo",
|
||||
"item.edit.bitstreams.save-button": "Save",
|
||||
"item.edit.bitstreams.upload-button": "Upload",
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.upload-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger mr-1" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
[disabled]="!(hasChanges() | async) || submitting"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
||||
@@ -16,7 +16,7 @@
|
||||
class="fas fa-undo-alt"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || submitting"
|
||||
(click)="submit()"><i
|
||||
class="fas fa-save"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.save-button" | translate}}</span>
|
||||
@@ -31,7 +31,6 @@
|
||||
<div class="col-6 col-sm-5 col-md-4 col-lg-3 text-center row-element">{{'item.edit.bitstreams.headers.actions' | translate}}</div>
|
||||
</div>
|
||||
<ds-item-edit-bitstream-bundle *ngFor="let bundle of bundles"
|
||||
[url]="url"
|
||||
[bundle]="bundle"
|
||||
[item]="item">
|
||||
</ds-item-edit-bitstream-bundle>
|
||||
@@ -45,7 +44,7 @@
|
||||
<div class="button-row bottom">
|
||||
<div class="mt-4 float-right">
|
||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
[disabled]="!(hasChanges() | async) || submitting"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
||||
@@ -55,7 +54,7 @@
|
||||
class="fas fa-undo-alt"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || submitting"
|
||||
(click)="submit()"><i
|
||||
class="fas fa-save"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.save-button" | translate}}</span>
|
||||
|
@@ -10,8 +10,8 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
|
||||
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
||||
import { hasValue, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { zip as observableZip } from 'rxjs';
|
||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { zip as observableZip, combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
||||
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
@@ -52,6 +52,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
pageSize: 9999
|
||||
} as any;
|
||||
|
||||
/**
|
||||
* Are we currently submitting the changes?
|
||||
* Used to disable any action buttons until the submit finishes
|
||||
*/
|
||||
submitting = false;
|
||||
|
||||
/**
|
||||
* A subscription that checks when the item is deleted in cache and reloads the item by sending a new request
|
||||
* This is used to update the item in cache after bitstreams are deleted
|
||||
@@ -113,6 +119,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
).subscribe((itemRD: RemoteData<Item>) => {
|
||||
if (hasValue(itemRD)) {
|
||||
this.item = itemRD.payload;
|
||||
this.postItemInit();
|
||||
this.initializeOriginalFields();
|
||||
this.initializeUpdates();
|
||||
this.cdRef.detectChanges();
|
||||
@@ -122,41 +129,74 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
|
||||
/**
|
||||
* Submit the current changes
|
||||
* Bitstreams that were dragged around send out a patch request with move operations to the rest API
|
||||
* Bitstreams marked as deleted send out a delete request to the rest API
|
||||
* Display notifications and reset the current item/updates
|
||||
*/
|
||||
submit() {
|
||||
const removedBitstreams$ = this.bundles$.pipe(
|
||||
this.submitting = true;
|
||||
const bundlesOnce$ = this.bundles$.pipe(take(1));
|
||||
|
||||
// Fetch all move operations for each bundle
|
||||
const moveOperations$ = bundlesOnce$.pipe(
|
||||
switchMap((bundles: Bundle[]) => observableZip(...bundles.map((bundle: Bundle) =>
|
||||
this.objectUpdatesService.getMoveOperations(bundle.self).pipe(
|
||||
take(1),
|
||||
map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, {
|
||||
from: `/_links/bitstreams${operation.from}/href`,
|
||||
path: `/_links/bitstreams${operation.path}/href`
|
||||
}))])
|
||||
)
|
||||
)))
|
||||
);
|
||||
|
||||
// Send out an immediate patch request for each bundle
|
||||
const patchResponses$ = observableCombineLatest(bundlesOnce$, moveOperations$).pipe(
|
||||
switchMap(([bundles, moveOperationList]: [Bundle[], Operation[][]]) =>
|
||||
observableZip(...bundles.map((bundle: Bundle, index: number) => {
|
||||
if (isNotEmpty(moveOperationList[index])) {
|
||||
return this.bundleService.immediatePatch(bundle, moveOperationList[index]);
|
||||
} else {
|
||||
return observableOf(undefined);
|
||||
}
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch all removed bitstreams from the object update service
|
||||
const removedBitstreams$ = bundlesOnce$.pipe(
|
||||
switchMap((bundles: Bundle[]) => observableZip(
|
||||
...bundles.map((bundle: Bundle) => this.objectUpdatesService.getFieldUpdates(bundle.self, [], true))
|
||||
)),
|
||||
map((fieldUpdates: FieldUpdates[]) => ([] as FieldUpdate[]).concat(
|
||||
...fieldUpdates.map((updates: FieldUpdates) => Object.values(updates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE))
|
||||
)),
|
||||
map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field)),
|
||||
isNotEmptyOperator()
|
||||
map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field))
|
||||
);
|
||||
|
||||
removedBitstreams$.pipe(
|
||||
// Send out delete requests for all deleted bitstreams
|
||||
const removedResponses$ = removedBitstreams$.pipe(
|
||||
take(1),
|
||||
switchMap((removedBistreams: Bitstream[]) => observableZip(...removedBistreams.map((bitstream: Bitstream) => this.bitstreamService.deleteAndReturnResponse(bitstream))))
|
||||
).subscribe((responses: RestResponse[]) => {
|
||||
this.displayNotifications(responses);
|
||||
this.reset();
|
||||
});
|
||||
switchMap((removedBistreams: Bitstream[]) => {
|
||||
if (isNotEmpty(removedBistreams)) {
|
||||
return observableZip(...removedBistreams.map((bitstream: Bitstream) => this.bitstreamService.deleteAndReturnResponse(bitstream)));
|
||||
} else {
|
||||
return observableOf(undefined);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => {
|
||||
bundles.forEach((bundle: Bundle) => {
|
||||
this.objectUpdatesService.getMoveOperations(bundle.self).pipe(
|
||||
take(1),
|
||||
isNotEmptyOperator(),
|
||||
map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, {
|
||||
from: `/_links/bitstreams${operation.from}/href`,
|
||||
path: `/_links/bitstreams${operation.path}/href`
|
||||
}))])
|
||||
).subscribe((operations: Operation[]) => this.bundleService.patch(bundle.self, operations));
|
||||
});
|
||||
// Perform the setup actions from above in order and display notifications
|
||||
patchResponses$.pipe(
|
||||
switchMap((responses: RestResponse[]) => {
|
||||
this.displayNotifications('item.edit.bitstreams.notifications.move', responses);
|
||||
return removedResponses$
|
||||
}),
|
||||
take(1)
|
||||
).subscribe((responses: RestResponse[]) => {
|
||||
this.displayNotifications('item.edit.bitstreams.notifications.remove', responses);
|
||||
this.reset();
|
||||
this.submitting = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,17 +204,20 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
* Display notifications
|
||||
* - Error notification for each failed response with their message
|
||||
* - Success notification in case there's at least one successful response
|
||||
* @param responses
|
||||
* @param key The i18n key for the notification messages
|
||||
* @param responses The returned responses to display notifications for
|
||||
*/
|
||||
displayNotifications(responses: RestResponse[]) {
|
||||
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful);
|
||||
const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful);
|
||||
displayNotifications(key: string, responses: RestResponse[]) {
|
||||
if (isNotEmpty(responses)) {
|
||||
const failedResponses = responses.filter((response: RestResponse) => hasValue(response) && !response.isSuccessful);
|
||||
const successfulResponses = responses.filter((response: RestResponse) => hasValue(response) && response.isSuccessful);
|
||||
|
||||
failedResponses.forEach((response: ErrorResponse) => {
|
||||
this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage);
|
||||
this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage);
|
||||
});
|
||||
if (successfulResponses.length > 0) {
|
||||
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved'));
|
||||
this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,8 +273,14 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
* Remove the current item's cache from object- and request-cache
|
||||
*/
|
||||
refreshItemCache() {
|
||||
this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => {
|
||||
bundles.forEach((bundle: Bundle) => {
|
||||
this.objectCache.remove(bundle.self);
|
||||
this.requestService.removeByHrefSubstring(bundle.self);
|
||||
});
|
||||
this.objectCache.remove(this.item.self);
|
||||
this.requestService.removeByHrefSubstring(this.item.self);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -21,7 +21,6 @@
|
||||
'bg-white': updateValue.changeType === undefined
|
||||
}">
|
||||
<ds-item-edit-bitstream [fieldUpdate]="updateValue"
|
||||
[url]="url"
|
||||
[bundleUrl]="bundle.self">
|
||||
<button disabled slot="drag-handle" class="drag-handle btn btn-outline-secondary btn-sm" cdkDragHandle>
|
||||
<i class="fas fa-grip-vertical fa-fw" [title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
|
||||
|
@@ -40,11 +40,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit {
|
||||
*/
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* The current url of this page
|
||||
*/
|
||||
@Input() url: string;
|
||||
|
||||
/**
|
||||
* The bitstreams within this bundle retrieved from the REST API
|
||||
*/
|
||||
|
@@ -28,11 +28,6 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit {
|
||||
*/
|
||||
@Input() fieldUpdate: FieldUpdate;
|
||||
|
||||
/**
|
||||
* The current url of this page
|
||||
*/
|
||||
@Input() url: string;
|
||||
|
||||
/**
|
||||
* The url of the bundle
|
||||
*/
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
FindAllOptions,
|
||||
FindAllRequest,
|
||||
FindByIDRequest,
|
||||
GetRequest
|
||||
GetRequest, PatchRequest
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
@@ -207,6 +207,32 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
this.objectCache.addPatch(href, operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send out an immediate patch request, instead of adding to the object cache first
|
||||
* This is useful in cases where you need the returned response and an object cache update is not needed
|
||||
* @param dso The dso to send the patch to
|
||||
* @param operations The patch operations
|
||||
*/
|
||||
immediatePatch(dso: T, operations: Operation[]): Observable<RestResponse> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
|
||||
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
||||
map((endpoint: string) => this.getIDHref(endpoint, dso.uuid)));
|
||||
|
||||
hrefObs.pipe(
|
||||
find((href: string) => hasValue(href)),
|
||||
map((href: string) => {
|
||||
const request = new PatchRequest(requestId, href, operations);
|
||||
this.requestService.configure(request);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
return this.requestService.getByUUID(requestId).pipe(
|
||||
find((request: RequestEntry) => request.completed),
|
||||
map((request: RequestEntry) => request.response)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new patch to the object cache
|
||||
* The patch is derived from the differences between the given object and its version in the object cache
|
||||
|
Reference in New Issue
Block a user