diff --git a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx index 75eccaabf2..796d5912d3 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx @@ -24,6 +24,7 @@ import { SnackbarWrapper } from './SnackbarMarkups/SnackbarWrapper'; export interface ProcessSnackbarProps extends INotificationProcessExtraProps { closeDelay?: number; displayDelay?: number; + onCancel?: () => void | Promise; } export const ProcessSnackbar: NotificationComponent = observer(function ProcessSnackbar({ @@ -31,6 +32,7 @@ export const ProcessSnackbar: NotificationComponent = obse displayDelay = 750, notification, state, + onCancel, }) { const { error, title, message, status } = state!; @@ -67,6 +69,12 @@ export const ProcessSnackbar: NotificationComponent = obse {translate('ui_errors_details')} )} + + {onCancel && status === ENotificationType.Loading && ( + + )} diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 2321167558..16abffab17 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -18,6 +18,7 @@ export default [ ['ui_processing_loading', 'Loading...'], ['ui_processing_cancel', 'Cancel'], ['ui_processing_canceling', 'Cancelling...'], + ['ui_processing_canceled', 'Canceled'], ['ui_processing_reload', 'Reload'], ['ui_processing_retry', 'Retry'], ['ui_processing_ok', 'Ok'], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index 310262e88f..b11450c90d 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -18,6 +18,7 @@ export default [ ['ui_processing_loading', 'Caricamento...'], ['ui_processing_cancel', 'Annulla'], ['ui_processing_canceling', 'Annullamento...'], + ['ui_processing_canceled', 'Canceled'], ['ui_processing_retry', 'Riprova'], ['ui_processing_ok', 'Ok'], ['ui_processing_create', 'Crea'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index cd91956513..ce1eff2a27 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -16,6 +16,7 @@ export default [ ['ui_processing_loading', 'Загрузка...'], ['ui_processing_cancel', 'Отменить'], ['ui_processing_canceling', 'Отмена...'], + ['ui_processing_canceled', 'Отменено'], ['ui_processing_reload', 'Перезагрузить'], ['ui_processing_retry', 'Повторить'], ['ui_processing_ok', 'Принять'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 7c9ce76141..c7835f0808 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -18,6 +18,7 @@ export default [ ['ui_processing_loading', '加载中...'], ['ui_processing_cancel', '取消'], ['ui_processing_canceling', '取消中...'], + ['ui_processing_canceled', 'Canceled'], ['ui_processing_retry', '重试'], ['ui_processing_ok', '好'], ['ui_processing_create', '创建'], diff --git a/webapp/packages/core-sdk/src/CustomGraphQLClient.ts b/webapp/packages/core-sdk/src/CustomGraphQLClient.ts index e4ff2a8a6f..33fa1ba8e7 100644 --- a/webapp/packages/core-sdk/src/CustomGraphQLClient.ts +++ b/webapp/packages/core-sdk/src/CustomGraphQLClient.ts @@ -5,7 +5,7 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import axios, { AxiosProgressEvent, AxiosResponse, isAxiosError } from 'axios'; +import axios, { AxiosProgressEvent, AxiosResponse, CanceledError, isAxiosError, isCancel } from 'axios'; import { ClientError, GraphQLClient, RequestDocument, RequestOptions, resolveRequestDocument, Variables } from 'graphql-request'; import { GQLError } from './GQLError'; @@ -36,10 +36,11 @@ export class CustomGraphQLClient extends GraphQLClient { query?: string, variables?: V, onUploadProgress?: (event: UploadProgressEvent) => void, + signal?: AbortSignal, ): Promise { return this.interceptors.reduce( (accumulator, interceptor) => interceptor(accumulator), - this.overrideFilesUpload(url, file, query, variables, onUploadProgress), + this.overrideFilesUpload(url, file, query, variables, onUploadProgress, signal), ); } @@ -126,6 +127,7 @@ export class CustomGraphQLClient extends GraphQLClient { query?: string, variables?: V, onUploadProgress?: (event: UploadProgressEvent) => void, + signal?: AbortSignal, ): Promise { this.blockRequestsReasonHandler(); try { @@ -146,6 +148,7 @@ export class CustomGraphQLClient extends GraphQLClient { } const response = await axios.postForm/**/ (url, data, { + signal, onUploadProgress, responseType: 'json', }); @@ -156,6 +159,10 @@ export class CustomGraphQLClient extends GraphQLClient { return response.data; } catch (error: any) { + if (isCancel(error)) { + throw new CanceledError('ui_processing_canceled'); + } + if (isAxiosError(error) && error.response?.data.message) { throw new ServerInternalError({ ...error, message: error.response.data.message }); } diff --git a/webapp/packages/core-sdk/src/Extensions/uploadResultDataExtension.ts b/webapp/packages/core-sdk/src/Extensions/uploadResultDataExtension.ts index 961e6c3aa9..2655163cc4 100644 --- a/webapp/packages/core-sdk/src/Extensions/uploadResultDataExtension.ts +++ b/webapp/packages/core-sdk/src/Extensions/uploadResultDataExtension.ts @@ -19,6 +19,7 @@ export interface IUploadResultDataExtension { processorId: string, file: File, onUploadProgress?: (event: UploadProgressEvent) => void, + signal?: AbortSignal, ) => Promise; } @@ -32,6 +33,7 @@ export function uploadResultDataExtension(client: CustomGraphQLClient): IUploadR processorId: string, file: File, onUploadProgress?: (event: UploadProgressEvent) => void, + signal?: AbortSignal, ): Promise { return client.uploadFile( GlobalConstants.absoluteServiceUrl('data', 'import'), @@ -39,6 +41,7 @@ export function uploadResultDataExtension(client: CustomGraphQLClient): IUploadR undefined, { connectionId, contextId, projectId, resultsId, processorId }, onUploadProgress, + signal, ); }, }; diff --git a/webapp/packages/plugin-data-import/src/DataImportService.ts b/webapp/packages/plugin-data-import/src/DataImportService.ts index 4dacb4e678..2114a402e9 100644 --- a/webapp/packages/plugin-data-import/src/DataImportService.ts +++ b/webapp/packages/plugin-data-import/src/DataImportService.ts @@ -39,28 +39,49 @@ export class DataImportService { } async importData(connectionId: string, contextId: string, projectId: string, resultsId: string, processorId: string, file: File) { + const abortController = new AbortController(); + let cancelImplementation: (() => void | Promise) | null; + + function cancel() { + cancelImplementation?.(); + } + const { controller, notification } = this.notificationService.processNotification( () => ProcessSnackbar, - {}, - { title: 'plugin_data_import_process_title', message: file.name }, + { + onCancel: cancel, + }, + { title: 'plugin_data_import_process_title', message: file.name, onClose: cancel }, ); try { - const result = await this.graphQLService.sdk.uploadResultData(connectionId, contextId, projectId, resultsId, processorId, file, event => { - if (event.total !== undefined) { - const percentCompleted = getProgressPercent(event.loaded, event.total); + cancelImplementation = () => abortController.abort(); - if (notification.message) { - controller.setMessage(`${percentCompleted}%\n${notification.message}`); + const result = await this.graphQLService.sdk.uploadResultData( + connectionId, + contextId, + projectId, + resultsId, + processorId, + file, + event => { + if (event.total !== undefined) { + const percentCompleted = getProgressPercent(event.loaded, event.total); + + if (notification.message) { + controller.setMessage(`${percentCompleted}%\n${notification.message}`); + } } - } - }); + }, + abortController.signal, + ); const task = this.asyncTaskInfoService.create(async () => { const { taskInfo } = await this.graphQLService.sdk.getAsyncTaskInfo({ taskId: result.id, removeOnFinish: false }); return taskInfo; }); + cancelImplementation = () => this.asyncTaskInfoService.cancel(task.id); controller.setMessage('plugin_data_import_process_file_processing_step_message'); await this.asyncTaskInfoService.run(task); @@ -69,6 +90,8 @@ export class DataImportService { } catch (exception: any) { controller.reject(exception, 'plugin_data_import_process_fail'); return false; + } finally { + cancelImplementation = null; } } }