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.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.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.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.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.outdated.title": "Changes outdated",
|
||||||
"item.edit.bitstreams.notifications.saved.content": "Your changes to this item's bitstreams were saved.",
|
"item.edit.bitstreams.notifications.remove.failed.title": "Error deleting bitstream",
|
||||||
"item.edit.bitstreams.notifications.saved.title": "Changes saved",
|
"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.reinstate-button": "Undo",
|
||||||
"item.edit.bitstreams.save-button": "Save",
|
"item.edit.bitstreams.save-button": "Save",
|
||||||
"item.edit.bitstreams.upload-button": "Upload",
|
"item.edit.bitstreams.upload-button": "Upload",
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.upload-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.upload-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger mr-1" *ngIf="!(isReinstatable() | async)"
|
<button class="btn btn-danger mr-1" *ngIf="!(isReinstatable() | async)"
|
||||||
[disabled]="!(hasChanges() | async)"
|
[disabled]="!(hasChanges() | async) || submitting"
|
||||||
(click)="discard()"><i
|
(click)="discard()"><i
|
||||||
class="fas fa-times"></i>
|
class="fas fa-times"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
class="fas fa-undo-alt"></i>
|
class="fas fa-undo-alt"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || submitting"
|
||||||
(click)="submit()"><i
|
(click)="submit()"><i
|
||||||
class="fas fa-save"></i>
|
class="fas fa-save"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.save-button" | translate}}</span>
|
<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 class="col-6 col-sm-5 col-md-4 col-lg-3 text-center row-element">{{'item.edit.bitstreams.headers.actions' | translate}}</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-item-edit-bitstream-bundle *ngFor="let bundle of bundles"
|
<ds-item-edit-bitstream-bundle *ngFor="let bundle of bundles"
|
||||||
[url]="url"
|
|
||||||
[bundle]="bundle"
|
[bundle]="bundle"
|
||||||
[item]="item">
|
[item]="item">
|
||||||
</ds-item-edit-bitstream-bundle>
|
</ds-item-edit-bitstream-bundle>
|
||||||
@@ -45,7 +44,7 @@
|
|||||||
<div class="button-row bottom">
|
<div class="button-row bottom">
|
||||||
<div class="mt-4 float-right">
|
<div class="mt-4 float-right">
|
||||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
[disabled]="!(hasChanges() | async)"
|
[disabled]="!(hasChanges() | async) || submitting"
|
||||||
(click)="discard()"><i
|
(click)="discard()"><i
|
||||||
class="fas fa-times"></i>
|
class="fas fa-times"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
class="fas fa-undo-alt"></i>
|
class="fas fa-undo-alt"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.reinstate-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || submitting"
|
||||||
(click)="submit()"><i
|
(click)="submit()"><i
|
||||||
class="fas fa-save"></i>
|
class="fas fa-save"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.save-button" | translate}}</span>
|
<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 { TranslateService } from '@ngx-translate/core';
|
||||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
|
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
|
||||||
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
||||||
import { hasValue, isNotEmptyOperator } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||||
import { zip as observableZip } from 'rxjs';
|
import { zip as observableZip, combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
|
||||||
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
|
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
|
||||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
@@ -52,6 +52,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
pageSize: 9999
|
pageSize: 9999
|
||||||
} as any;
|
} 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
|
* 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
|
* 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>) => {
|
).subscribe((itemRD: RemoteData<Item>) => {
|
||||||
if (hasValue(itemRD)) {
|
if (hasValue(itemRD)) {
|
||||||
this.item = itemRD.payload;
|
this.item = itemRD.payload;
|
||||||
|
this.postItemInit();
|
||||||
this.initializeOriginalFields();
|
this.initializeOriginalFields();
|
||||||
this.initializeUpdates();
|
this.initializeUpdates();
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
@@ -122,41 +129,74 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit the current changes
|
* 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
|
* Bitstreams marked as deleted send out a delete request to the rest API
|
||||||
* Display notifications and reset the current item/updates
|
* Display notifications and reset the current item/updates
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
const removedBitstreams$ = this.bundles$.pipe(
|
this.submitting = true;
|
||||||
take(1),
|
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(
|
switchMap((bundles: Bundle[]) => observableZip(
|
||||||
...bundles.map((bundle: Bundle) => this.objectUpdatesService.getFieldUpdates(bundle.self, [], true))
|
...bundles.map((bundle: Bundle) => this.objectUpdatesService.getFieldUpdates(bundle.self, [], true))
|
||||||
)),
|
)),
|
||||||
map((fieldUpdates: FieldUpdates[]) => ([] as FieldUpdate[]).concat(
|
map((fieldUpdates: FieldUpdates[]) => ([] as FieldUpdate[]).concat(
|
||||||
...fieldUpdates.map((updates: FieldUpdates) => Object.values(updates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE))
|
...fieldUpdates.map((updates: FieldUpdates) => Object.values(updates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE))
|
||||||
)),
|
)),
|
||||||
map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field)),
|
map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field))
|
||||||
isNotEmptyOperator()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
removedBitstreams$.pipe(
|
// Send out delete requests for all deleted bitstreams
|
||||||
|
const removedResponses$ = removedBitstreams$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((removedBistreams: Bitstream[]) => observableZip(...removedBistreams.map((bitstream: Bitstream) => this.bitstreamService.deleteAndReturnResponse(bitstream))))
|
switchMap((removedBistreams: Bitstream[]) => {
|
||||||
).subscribe((responses: RestResponse[]) => {
|
if (isNotEmpty(removedBistreams)) {
|
||||||
this.displayNotifications(responses);
|
return observableZip(...removedBistreams.map((bitstream: Bitstream) => this.bitstreamService.deleteAndReturnResponse(bitstream)));
|
||||||
this.reset();
|
} else {
|
||||||
});
|
return observableOf(undefined);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => {
|
// Perform the setup actions from above in order and display notifications
|
||||||
bundles.forEach((bundle: Bundle) => {
|
patchResponses$.pipe(
|
||||||
this.objectUpdatesService.getMoveOperations(bundle.self).pipe(
|
switchMap((responses: RestResponse[]) => {
|
||||||
take(1),
|
this.displayNotifications('item.edit.bitstreams.notifications.move', responses);
|
||||||
isNotEmptyOperator(),
|
return removedResponses$
|
||||||
map((operations: MoveOperation[]) => [...operations.map((operation: MoveOperation) => Object.assign(operation, {
|
}),
|
||||||
from: `/_links/bitstreams${operation.from}/href`,
|
take(1)
|
||||||
path: `/_links/bitstreams${operation.path}/href`
|
).subscribe((responses: RestResponse[]) => {
|
||||||
}))])
|
this.displayNotifications('item.edit.bitstreams.notifications.remove', responses);
|
||||||
).subscribe((operations: Operation[]) => this.bundleService.patch(bundle.self, operations));
|
this.reset();
|
||||||
});
|
this.submitting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,17 +204,20 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
* Display notifications
|
* Display notifications
|
||||||
* - Error notification for each failed response with their message
|
* - Error notification for each failed response with their message
|
||||||
* - Success notification in case there's at least one successful response
|
* - 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[]) {
|
displayNotifications(key: string, responses: RestResponse[]) {
|
||||||
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful);
|
if (isNotEmpty(responses)) {
|
||||||
const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful);
|
const failedResponses = responses.filter((response: RestResponse) => hasValue(response) && !response.isSuccessful);
|
||||||
|
const successfulResponses = responses.filter((response: RestResponse) => hasValue(response) && response.isSuccessful);
|
||||||
|
|
||||||
failedResponses.forEach((response: ErrorResponse) => {
|
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) {
|
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
|
* Remove the current item's cache from object- and request-cache
|
||||||
*/
|
*/
|
||||||
refreshItemCache() {
|
refreshItemCache() {
|
||||||
this.objectCache.remove(this.item.self);
|
this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => {
|
||||||
this.requestService.removeByHrefSubstring(this.item.self);
|
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
|
'bg-white': updateValue.changeType === undefined
|
||||||
}">
|
}">
|
||||||
<ds-item-edit-bitstream [fieldUpdate]="updateValue"
|
<ds-item-edit-bitstream [fieldUpdate]="updateValue"
|
||||||
[url]="url"
|
|
||||||
[bundleUrl]="bundle.self">
|
[bundleUrl]="bundle.self">
|
||||||
<button disabled slot="drag-handle" class="drag-handle btn btn-outline-secondary btn-sm" cdkDragHandle>
|
<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>
|
<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;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current url of this page
|
|
||||||
*/
|
|
||||||
@Input() url: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bitstreams within this bundle retrieved from the REST API
|
* The bitstreams within this bundle retrieved from the REST API
|
||||||
*/
|
*/
|
||||||
|
@@ -28,11 +28,6 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() fieldUpdate: FieldUpdate;
|
@Input() fieldUpdate: FieldUpdate;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current url of this page
|
|
||||||
*/
|
|
||||||
@Input() url: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url of the bundle
|
* The url of the bundle
|
||||||
*/
|
*/
|
||||||
|
@@ -17,7 +17,7 @@ import {
|
|||||||
FindAllOptions,
|
FindAllOptions,
|
||||||
FindAllRequest,
|
FindAllRequest,
|
||||||
FindByIDRequest,
|
FindByIDRequest,
|
||||||
GetRequest
|
GetRequest, PatchRequest
|
||||||
} from './request.models';
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.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);
|
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
|
* 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
|
* 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