From 6f410d7132741e46e9625080c5c426d5c63bedfb Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Tue, 9 Jan 2024 13:04:20 +0100 Subject: [PATCH] Cancellable request improvements --- src/components/JobPanel.vue | 52 +++++------------- src/components/cancellableRequest.js | 76 +++++++++++++++++++++++++++ src/components/modals/WizardModal.vue | 18 ++++++- 3 files changed, 105 insertions(+), 41 deletions(-) create mode 100644 src/components/cancellableRequest.js diff --git a/src/components/JobPanel.vue b/src/components/JobPanel.vue index b076b9fb..0233a5ff 100644 --- a/src/components/JobPanel.vue +++ b/src/components/JobPanel.vue @@ -26,7 +26,8 @@ import EventBusMixin from './EventBusMixin'; import WorkPanelMixin from './WorkPanelMixin'; import SyncButton from './SyncButton.vue'; import Utils from '../utils.js'; -import { AbortController, Job } from '@openeo/js-client'; +import { Job } from '@openeo/js-client'; +import { cancellableRequest, showCancellableRequestError, CancellableRequestError } from './cancellableRequest'; const WorkPanelMixinInstance = WorkPanelMixin('jobs', 'batch job', 'batch jobs'); @@ -39,8 +40,7 @@ export default { data() { return { watchers: {}, - jobUpdater: null, - runId: 0 + jobUpdater: null }; }, mounted() { @@ -145,48 +145,20 @@ export default { await this.queueJob(job); }, async executeProcess() { - let abortController = new AbortController(); - let snotifyConfig = { - timeout: 0, - type: 'async', - buttons: [{ - text: 'Cancel', - action: toast => { - abortController.abort(); - this.$snotify.remove(toast.id, true); - } - }] + const callback = async (abortController) => { + const result = await this.connection.computeResult(this.process, null, null, abortController); + this.broadcast('viewSyncResult', result); }; - let toast; try { - this.runId++; - let message = "A process is currently executed synchronously..."; - let title = `Run #${this.runId}`; - let endlessPromise = () => new Promise(() => {}); // Pass a promise to snotify that never resolves as we manually close the toast - toast = this.$snotify.async(message, title, endlessPromise, snotifyConfig); - let result = await this.connection.computeResult(this.process, null, null, abortController); - this.broadcast('viewSyncResult', result); - } catch(error) { - if (axios.isCancel(error)) { - // Do nothing, we expected the cancellation - } - else if (typeof error.message === 'string' && Utils.isObject(error.response) && [400,500].includes(error.response.status)) { - this.broadcast('viewLogs', [{ - id: error.id, - code: error.code, - level: 'error', - message: error.message, - links: error.links || [] - }]); - Utils.error(this, "Synchronous processing failed. Please see the logs for details.", "Processing Error"); + await cancellableRequest(this, callback, 'Run'); + } catch (error) { + if (error instanceof CancellableRequestError) { + showCancellableRequestError(this, error); } else { - Utils.exception(this, error, "Server Error"); - } - } finally { - if (toast) { - this.$snotify.remove(toast.id, true); + Utils.exception(this, error); } + } }, jobCreated(job) { diff --git a/src/components/cancellableRequest.js b/src/components/cancellableRequest.js new file mode 100644 index 00000000..58601259 --- /dev/null +++ b/src/components/cancellableRequest.js @@ -0,0 +1,76 @@ +import { AbortController } from '@openeo/js-client'; +import Utils from '../utils'; + +export class CancellableRequestError extends Error { + constructor(message, title = null, cause = null, close = true, isError = true) { + super(message, {cause}); + this.title = title; + this.close = close; + this.isError = isError; + } +} + +export function showCancellableRequestError(vm, error) { + if (error instanceof CancellableRequestError) { + if (error.isError) { + Utils.error(vm, error.message, error.title); + } + else { + Utils.ok(vm, error.message, error.title); + } + } +} + +let runIds = {}; +export async function cancellableRequest(vm, callback, entity) { + if (!runIds[entity]) { + runIds[entity] = 1; + } + else { + runIds[entity]++; + } + + const abortController = new AbortController(); + const snotifyConfig = Object.assign({}, vm.$config.snotifyDefaults, { + timeout: 0, + type: 'async', + buttons: [{ + text: 'Cancel', + action: () => { + abortController.abort(); + } + }] + }); + + let toast; + const toastTitle = `${entity} #${runIds[entity]}`; + try { + const message = `Processing in progress, please wait...`; + // Pass a promise to snotify that never resolves as we manually close the toast + const endlessPromise = () => new Promise(() => {}); + toast = vm.$snotify.async(message, toastTitle, endlessPromise, snotifyConfig); + + await callback(abortController); + } catch(error) { + if (axios.isCancel(error)) { + throw new CancellableRequestError(`Cancelled successfully`, toastTitle, error, false, false); + } + else if (typeof error.message === 'string' && Utils.isObject(error.response) && [400,500].includes(error.response.status)) { + vm.broadcast('viewLogs', [{ + id: error.id, + code: error.code, + level: 'error', + message: error.message, + links: error.links || [] + }]); + Utils.error(vm, `${entity} failed. Please see the logs for details.`, toastTitle); + } + else { + throw new CancellableRequestError(error.message, toastTitle, error, false); + } + } finally { + if (toast) { + vm.$snotify.remove(toast.id, true); + } + } +} \ No newline at end of file diff --git a/src/components/modals/WizardModal.vue b/src/components/modals/WizardModal.vue index 0d7430c5..0487a32c 100644 --- a/src/components/modals/WizardModal.vue +++ b/src/components/modals/WizardModal.vue @@ -60,6 +60,7 @@ import WizardStep from '../wizards/components/WizardStep.vue'; import Utils from '../../utils'; import Config from '../../../config'; import EventBusMixin from '../EventBusMixin'; +import { CancellableRequestError } from '../cancellableRequest'; const wizards = Config.supportedWizards || []; let components = { @@ -248,7 +249,22 @@ export default { else if (this.isLastStep) { this.$refs.component.finish() .then(this.close) - .catch(error => Utils.exception(this, error)); + .catch(error => { + if (error instanceof CancellableRequestError) { + if (error.isError) { + Utils.exception(this, error, error.title); + } + else { + Utils.ok(this, error.message, error.title); + } + if (error.close) { + this.close(); + } + } + else { + Utils.exception(this, error); + } + }); } } this.beforeTabChange(this.activeTabIndex, cb);