diff --git a/webapp/packages/core-app/src/shared/InlineEditor/InlineEditor.tsx b/webapp/packages/core-app/src/shared/InlineEditor/InlineEditor.tsx index 34a796d118..224aaba6f5 100644 --- a/webapp/packages/core-app/src/shared/InlineEditor/InlineEditor.tsx +++ b/webapp/packages/core-app/src/shared/InlineEditor/InlineEditor.tsx @@ -33,6 +33,7 @@ export interface InlineEditorProps { hideSave?: boolean; hideCancel?: boolean; edited?: boolean; + disabled?: boolean; autofocus?: boolean; onChange: (value: string) => void; onSave: () => void; @@ -52,6 +53,7 @@ export const InlineEditor = observer(function InlineEditor({ hideSave, hideCancel, edited = false, + disabled, autofocus, onChange, onSave, @@ -103,6 +105,7 @@ export const InlineEditor = observer(function InlineEditor({ tabIndex={tabIndex} placeholder={placeholder} autoComplete="off" + disabled={disabled} onChange={handleChange} onKeyDown={handleKeyDown} /> diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 693021b81d..7cb5134da0 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -48,6 +48,7 @@ export * from './StaticImage'; export * from './TextPlaceholder'; export * from './useClipboard'; export * from './useFocus'; +export * from './useStateDelay'; export * from './useErrorDetails'; export * from './useAppLoadingScreen'; export * from './BlocksLocaleService'; diff --git a/webapp/packages/core-blocks/src/useStateDelay.tsx b/webapp/packages/core-blocks/src/useStateDelay.tsx index 9f0c400366..9ff685b7a8 100644 --- a/webapp/packages/core-blocks/src/useStateDelay.tsx +++ b/webapp/packages/core-blocks/src/useStateDelay.tsx @@ -14,16 +14,18 @@ export function useStateDelay(state: boolean, delay: number, callback?: () => vo callbackRef.current = callback; useEffect(() => { - if (!state) { + if (state === delayedState) { return; } const timerId = setTimeout(() => { - setState(true); - callbackRef.current?.(); + setState(state); + if (state) { + callbackRef.current?.(); + } }, delay); return () => clearTimeout(timerId); - }, [state, delay]); + }, [state, delayedState, delay]); return delayedState; } diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index e4faaeaa5e..6e1e3ee20f 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -14,6 +14,7 @@ export const defaultENLocale = [ ['ui_processing_create', 'Create'], ['ui_processing_save', 'Save'], ['ui_error_message', 'Error:'], + ['ui_error_close', 'Close'], ['ui_errors_details', 'Details'], ['ui_search', 'Search...'], ['ui_delete', 'Delete'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index df817dc20a..4fabefd7f0 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -12,6 +12,7 @@ export const defaultRULocale = [ ['ui_processing_create', 'Создать'], ['ui_processing_save', 'Сохранить'], ['ui_error_message', 'Ошибка:'], + ['ui_error_close', 'Закрыть'], ['ui_errors_details', 'Информация'], ['ui_search', 'Поиск...'], ['ui_delete', 'Удалить'], diff --git a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorModel.ts b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorModel.ts index df2b24f9f3..41f3a668b2 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorModel.ts +++ b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorModel.ts @@ -40,13 +40,13 @@ export class ErrorModel { }); this.textToCopy = error.isTextBody - ? error.errorText + ? error.errorMessage : this.textToCopy = this.errors .map(error => `${error.message}\n${error.stackTrace}`) .join('------------------\n'); if (error.isTextBody) { - this.htmlBody = error.errorText; + this.htmlBody = error.errorMessage; } } else if (error instanceof ServerInternalError) { this.errors = [ diff --git a/webapp/packages/core-sdk/src/DetailsError.ts b/webapp/packages/core-sdk/src/DetailsError.ts new file mode 100644 index 0000000000..ffe6bc0016 --- /dev/null +++ b/webapp/packages/core-sdk/src/DetailsError.ts @@ -0,0 +1,18 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export abstract class DetailsError extends Error { + errorMessage: string; + constructor(message?: string) { + super(message); + this.name = 'Details Error'; + this.errorMessage = message || 'Error'; + } + + abstract hasDetails(): boolean; +} diff --git a/webapp/packages/core-sdk/src/GQLError.ts b/webapp/packages/core-sdk/src/GQLError.ts index 8f53dce696..d74889221b 100644 --- a/webapp/packages/core-sdk/src/GQLError.ts +++ b/webapp/packages/core-sdk/src/GQLError.ts @@ -11,10 +11,12 @@ import type { GraphQLResponse, GraphQLRequestContext, ClientError } from 'graphql-request/dist/src/types'; -export class GQLError extends Error { +import { DetailsError } from './DetailsError'; + +export class GQLError extends DetailsError { response: GraphQLResponse; request: GraphQLRequestContext; - errorText: string; + errorMessage: string; errorCode?: string; isTextBody = false; // true when server returns not GQLError object but plain text or html error @@ -25,9 +27,9 @@ export class GQLError extends Error { this.request = clientError.request; if (typeof clientError.response.error === 'string') { this.isTextBody = true; - this.errorText = clientError.response.error; + this.errorMessage = clientError.response.error; } else { - this.errorText = clientError.response.errors?.map(e => e.message).join('\n') || 'unknown error'; + this.errorMessage = clientError.response.errors?.map(e => e.message).join('\n') || 'unknown error'; const firstError = clientError.response.errors?.[0]; this.errorCode = firstError?.extensions?.webErrorCode; diff --git a/webapp/packages/core-sdk/src/GQLErrorCatcher.ts b/webapp/packages/core-sdk/src/GQLErrorCatcher.ts index ce93181265..e31233cdad 100644 --- a/webapp/packages/core-sdk/src/GQLErrorCatcher.ts +++ b/webapp/packages/core-sdk/src/GQLErrorCatcher.ts @@ -8,12 +8,12 @@ import { observable, action, makeObservable } from 'mobx'; -import { GQLError } from './GQLError'; +import { DetailsError } from './DetailsError'; export class GQLErrorCatcher { hasDetails = false; responseMessage: string | null = null; - exception: GQLError | null = null; + exception: DetailsError | null = null; constructor() { makeObservable(this, { @@ -26,8 +26,8 @@ export class GQLErrorCatcher { } catch(exception: any): boolean { - if (exception instanceof GQLError) { - this.responseMessage = exception.errorText; + if (exception instanceof DetailsError) { + this.responseMessage = exception.errorMessage; this.hasDetails = exception.hasDetails(); this.exception = exception; return true; diff --git a/webapp/packages/core-sdk/src/ServerInternalError.ts b/webapp/packages/core-sdk/src/ServerInternalError.ts index 2564407e4d..8aaf9d7dd9 100644 --- a/webapp/packages/core-sdk/src/ServerInternalError.ts +++ b/webapp/packages/core-sdk/src/ServerInternalError.ts @@ -6,17 +6,23 @@ * you may not use this file except in compliance with the License. */ +import { DetailsError } from './DetailsError'; import type { ServerError } from './sdk'; -export class ServerInternalError extends Error implements ServerError { +export class ServerInternalError extends DetailsError implements ServerError { readonly errorCode?: string; readonly stackTrace?: string; readonly causedBy?: ServerError; constructor(error: ServerError) { super(error.message); + this.name = 'Server Internal Error'; this.errorCode = error.errorCode; this.stackTrace = error.stackTrace; this.causedBy = error.causedBy; } + + hasDetails(): boolean { + return this.stackTrace !== undefined && this.stackTrace.length > 0; + } } diff --git a/webapp/packages/core-sdk/src/getErrorDetails.ts b/webapp/packages/core-sdk/src/getErrorDetails.ts index 0a55679dad..b39ffce102 100644 --- a/webapp/packages/core-sdk/src/getErrorDetails.ts +++ b/webapp/packages/core-sdk/src/getErrorDetails.ts @@ -6,8 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { GQLError } from './GQLError'; -import { ServerInternalError } from './ServerInternalError'; +import { DetailsError } from './DetailsError'; export interface IErrorDetails { name: string; @@ -15,12 +14,12 @@ export interface IErrorDetails { hasDetails: boolean; } -export function hasDetails(error: Error): error is GQLError | ServerInternalError { - return error instanceof GQLError || error instanceof ServerInternalError; +export function hasDetails(error: Error): error is DetailsError { + return error instanceof DetailsError && error.hasDetails(); } -export function getErrorDetails(error: Error | GQLError): IErrorDetails { - const exceptionMessage = hasDetails(error) ? error.errorText : error.message || error.name; +export function getErrorDetails(error: Error | DetailsError): IErrorDetails { + const exceptionMessage = hasDetails(error) ? error.errorMessage : error.message || error.name; return { name: error.name, message: exceptionMessage, diff --git a/webapp/packages/core-sdk/src/index.ts b/webapp/packages/core-sdk/src/index.ts index 129c154e38..06d922ce7c 100644 --- a/webapp/packages/core-sdk/src/index.ts +++ b/webapp/packages/core-sdk/src/index.ts @@ -3,6 +3,7 @@ export * from './Resource/CachedMapResource'; export * from './Resource/CachedResource'; export * from './Resource/ResourceKeyList'; export * from './CustomGraphQLClient'; +export * from './DetailsError'; export * from './EnvironmentService'; export * from './EServerErrorCode'; export * from './getErrorDetails'; diff --git a/webapp/packages/core-utils/src/Promises/Deferred.ts b/webapp/packages/core-utils/src/Promises/Deferred.ts index b1556a1362..50304ceacf 100644 --- a/webapp/packages/core-utils/src/Promises/Deferred.ts +++ b/webapp/packages/core-utils/src/Promises/Deferred.ts @@ -90,7 +90,7 @@ export class Deferred { protected toCancelled(reason?: any): void { this.state = EDeferredState.CANCELLED; this.rejectionReason = reason; - this.promiseExecutor.reject(new PromiseCancelledError(reason)); + this.promiseExecutor.reject(reason); } protected toCancelling(): void { diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index b033762044..17900c203a 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -15,7 +15,7 @@ import { injectable, IInitializableController, IDestructibleController } from '@ import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLError, DatabaseAuthModel } from '@cloudbeaver/core-sdk'; +import { DatabaseAuthModel, DetailsError } from '@cloudbeaver/core-sdk'; import { TemplateConnectionsResource } from '../TemplateConnectionsResource'; @@ -43,7 +43,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl hasDetails = false; responseMessage: string | null = null; - private exception: GQLError | null = null; + private exception: DetailsError | null = null; private onClose!: () => void; private isDistructed = false; @@ -152,8 +152,8 @@ implements IInitializableController, IDestructibleController, IConnectionControl } private showError(exception: Error, message: string) { - if (exception instanceof GQLError && !this.isDistructed) { - this.responseMessage = exception.errorText; + if (exception instanceof DetailsError && !this.isDistructed) { + this.responseMessage = exception.errorMessage; this.hasDetails = exception.hasDetails(); this.exception = exception; } else { diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts index b07b1d6f91..083b1df905 100644 --- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts @@ -31,6 +31,9 @@ export class DataExportMenuService { isPresent(context) { return context.contextType === TableFooterMenuService.nodeContextType; }, + isDisabled(context) { + return context.data.model.isLoading() || !context.data.model.getResult(context.data.resultIndex); + }, order: 5, title: 'data_transfer_dialog_export', icon: 'table-export', diff --git a/webapp/packages/plugin-data-viewer/src/FetchTableDataAsyncProcess.ts b/webapp/packages/plugin-data-viewer/src/FetchTableDataAsyncProcess.ts index cd9eabc11e..64dceb814b 100644 --- a/webapp/packages/plugin-data-viewer/src/FetchTableDataAsyncProcess.ts +++ b/webapp/packages/plugin-data-viewer/src/FetchTableDataAsyncProcess.ts @@ -158,9 +158,7 @@ export class FetchTableDataAsyncProcess extends Deferred { private onError(error: Error, status?: string) { // if task failed to execute during cancelling - it means it was cancelled successfully if (this.getState() === EDeferredState.CANCELLING) { - this.toCancelled(); - const message = `Data fetch has been canceled${status ? `: ${status}` : ''}`; - this.notificationService.logException(error, 'Data fetch Error', message); + this.toCancelled(error); } else { this.toRejected(error); } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/DataModelWrapper.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/DataModelWrapper.ts index 2a4a973100..41da44b63d 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/DataModelWrapper.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/DataModelWrapper.ts @@ -10,11 +10,11 @@ import { observable, makeObservable } from 'mobx'; import type { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLError } from '@cloudbeaver/core-sdk'; +import { DetailsError } from '@cloudbeaver/core-sdk'; import type { IDataContainerOptions } from '../ContainerDataSource'; import { DatabaseDataModel } from '../DatabaseDataModel/DatabaseDataModel'; -import type { DatabaseDataAccessMode } from '../DatabaseDataModel/IDatabaseDataModel'; +import { DatabaseDataAccessMode } from '../DatabaseDataModel/IDatabaseDataModel'; import type { IDatabaseDataResult } from '../DatabaseDataModel/IDatabaseDataResult'; import type { IDatabaseDataSource } from '../DatabaseDataModel/IDatabaseDataSource'; import type { RowDiff } from './TableDataModel/EditedRow'; @@ -128,17 +128,34 @@ export class DataModelWrapper extends DatabaseDataModel { if (!this.isDataAvailable(offset, count)) { this.source.setSlice(offset, count); - this.results = await this.source.requestData(this.results); - await this.setDeprecatedModelData(); + this.clearErrors(); + try { + this.results = await this.source.requestData(this.results); + await this.setDeprecatedModelData(); + } catch (exception) { + this.showError(exception); + throw exception; + } } } + /** + * @deprecated will be refactored + */ showDetails = (): void => { if (this.exception) { this.commonDialogService.open(ErrorDetailsDialog, this.exception); } }; + /** + * @deprecated will be refactored + */ + clearErrors = (): void => { + this.errorMessage = ''; + this.exception = null; + }; + async dispose(): Promise { await this.source.dispose(); } @@ -199,14 +216,17 @@ export class DataModelWrapper extends DatabaseDataModel 1 + ? DatabaseDataAccessMode.Readonly + : this.access; } } private showError(exception: any) { this.exception = null; this.hasDetails = false; - if (exception instanceof GQLError) { - this.errorMessage = exception.errorText; + if (exception instanceof DetailsError) { + this.errorMessage = exception.errorMessage; this.exception = exception; this.hasDetails = exception.hasDetails(); } else { @@ -214,11 +234,6 @@ export class DataModelWrapper extends DatabaseDataModel - model.refresh()} /> + model.refresh()} + /> - + diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts index 90f62b0211..94e37e72a7 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts @@ -35,7 +35,7 @@ export class TableFooterMenuService { if (context.data.model.deprecatedModels.length === 0) { return true; } - return !context.data.model.getOldModel(context.data.resultIndex)?.isEdited(); + return context.data.model.isLoading() || !context.data.model.getOldModel(context.data.resultIndex)?.isEdited(); }, order: 1, title: 'ui_processing_save', diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableGrid.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableGrid.tsx index 1e48acacc1..da3bbad3de 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableGrid.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableGrid.tsx @@ -44,21 +44,6 @@ export const TableGrid = observer(function TableGrid({ const depModel = model.getOldModel(resultIndex); const translate = useTranslate(); - // TODO: probably must be implemented in presentation component - if (model.message.length > 0) { - return styled(styles)( - - {model.message} -

- {model.details && ( - - )} -
- ); - } - if (dataFormat !== presentation.dataFormat) { if (model.isLoading()) { return null; @@ -74,5 +59,7 @@ export const TableGrid = observer(function TableGrid({ return styled(styles)({translate('data_viewer_nodata_message')}); } - return styled(styles)(); + return styled(styles)( + + ); }); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx index 77451625d5..52ba9ac39d 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx @@ -48,23 +48,26 @@ export const TableWhereFilter = observer(function TableWhereFilter({ const [filterValue, setValue] = useState(() => context.source.options?.whereFilter || ''); const handleApply = useCallback(() => { + if (context.isLoading()) { + return; + } context.source.options!.whereFilter = filterValue; context.refresh(); }, [context, filterValue]); - const resetFilter = useCallback( - () => { - const applyNeeded = context.source.options?.whereFilter === filterValue; + const resetFilter = useCallback(() => { + if (context.isLoading()) { + return; + } + const applyNeeded = context.source.options?.whereFilter === filterValue; - setValue(''); + setValue(''); - if (applyNeeded) { - context.source.options!.whereFilter = ''; - context.refresh(); - } - }, - [context, filterValue] - ); + if (applyNeeded) { + context.source.options!.whereFilter = ''; + context.refresh(); + } + }, [context, filterValue]); return styled(useStyles(styles))( 1} simple onSave={handleApply} onUndo={resetFilter} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/PresentationTab.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/PresentationTab.tsx index 4c51343156..224c2f300d 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/PresentationTab.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/PresentationTab.tsx @@ -42,11 +42,16 @@ export const PresentationTab = observer(function PresentationTab({ style={verticalRotatedTabStyles} model={model} presentation={presentation} + disabled={model.isLoading()} /> ); } return styled(styles)( - + {presentation.title} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/TableLeftBar.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/TableLeftBar.tsx index cc15396d0c..3fc5cf7f35 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/TableLeftBar.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableLeftBar/TableLeftBar.tsx @@ -71,7 +71,12 @@ export const TableLeftBar = observer(function TableLeftBar({ {presentations.map(presentation => ( - + ))} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx index b4c19f5308..4a255aa082 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx @@ -8,11 +8,13 @@ import { observer } from 'mobx-react-lite'; import { PropsWithChildren, useCallback } from 'react'; -import styled, { css } from 'reshadow'; +import styled, { css, use } from 'reshadow'; -import { Loader, TextPlaceholder } from '@cloudbeaver/core-blocks'; +import { Button, Loader, TextPlaceholder, useStateDelay } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import { useTranslate } from '@cloudbeaver/core-localization'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; import { DataPresentationService } from '../DataPresentationService'; import { TableFooter } from './TableFooter/TableFooter'; @@ -21,26 +23,52 @@ import { TableHeader } from './TableHeader/TableHeader'; import { TableLeftBar } from './TableLeftBar/TableLeftBar'; import { TableViewerStorageService } from './TableViewerStorageService'; -const viewerStyles = css` - table-viewer { - position: relative; - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - } - table-content { - display: flex; - flex: 1; - overflow: hidden; - } - table-data, table-box { - display: flex; - flex: 1; - flex-direction: column; - overflow: hidden; - } -`; +const viewerStyles = composes( + css` + error { + composes: theme-background-surface from global; + } + `, + css` + table-viewer { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + } + table-content { + display: flex; + flex: 1; + overflow: hidden; + } + table-data, table-box { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + } + error { + position: absolute; + box-sizing: border-box; + width: 100%; + height: 100%; + white-space: pre-wrap; + padding: 16px; + overflow: auto; + z-index: 1; + opacity: 0; + transition: opacity 0.3s ease-in-out; + } + error[|animated] { + opacity: 1; + } + Button { + margin-right: 16px; + } + ` +); type TableViewerProps = PropsWithChildren<{ tableId: string; @@ -57,10 +85,13 @@ export const TableViewer = observer(function TableViewer({ className, onPresentationChange, }: TableViewerProps) { + const styles = useStyles(viewerStyles); const dataPresentationService = useService(DataPresentationService); const tableViewerStorageService = useService(TableViewerStorageService); const dataModel = tableViewerStorageService.get(tableId); const result = dataModel?.getResult(resultIndex); + const translate = useTranslate(); + const animated = useStateDelay(!!dataModel && dataModel.message.length !== 0, 1); const handlePresentationChange = useCallback((id: string) => { const presentation = dataPresentationService.get(id); @@ -87,7 +118,7 @@ export const TableViewer = observer(function TableViewer({ return There are no available presentation for data format: {dataFormat}; } - return styled(viewerStyles)( + return styled(styles)( @@ -105,16 +136,28 @@ export const TableViewer = observer(function TableViewer({ presentation={presentation} resultIndex={resultIndex} /> + + 0 && presentation.dataFormat === dataFormat} + onCancel={() => dataModel.source.cancel()} + /> - dataModel.source.cancel()} - /> ); }); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts index 400669b5b2..df42b8f235 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts @@ -11,7 +11,7 @@ import { Subject, Observable } from 'rxjs'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLError, SqlDataFilterConstraint } from '@cloudbeaver/core-sdk'; +import { DetailsError, SqlDataFilterConstraint } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; import { DatabaseDataAccessMode } from '../DatabaseDataModel/IDatabaseDataModel'; @@ -218,9 +218,9 @@ export class TableViewerModel { let hasDetails = false; let message = `${exception.name}: ${exception.message}`; - if (exception instanceof GQLError) { + if (exception instanceof DetailsError) { hasDetails = exception.hasDetails(); - message = exception.errorText; + message = exception.errorMessage; } const state = await this.commonDialogService.open( diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorGroupMetadata.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorGroupMetadata.ts deleted file mode 100644 index d5bc3e603a..0000000000 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorGroupMetadata.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * cloudbeaver - Cloud Database Manager - * Copyright (C) 2020 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { ResultDataFormat, SqlDataFilter } from '@cloudbeaver/core-sdk'; - -import { ISqlQueryParams } from './ISqlEditorTabState'; -import { SqlExecutionState } from './SqlExecutionState'; -import { SQLQueryExecutionProcess } from './SqlResultTabs/SQLQueryExecutionProcess'; - -export interface ISqlEditorGroupMetadata { - resultDataProcess: SQLQueryExecutionProcess; - start: ( - context: SqlExecutionState, - sqlQueryParams: ISqlQueryParams, - filter: SqlDataFilter, - dataFormat: ResultDataFormat, - ) => Promise; - dispose: (params: ISqlQueryParams) => Promise; -} diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts index 282f56a615..417958c4dc 100644 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts +++ b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { IExecutionContext } from '@cloudbeaver/plugin-data-viewer'; +import type { IExecutionContext } from '@cloudbeaver/plugin-data-viewer'; export interface ISqlQueryParams extends IExecutionContext { query: string; diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts index 9ea1d3f17d..8d6b56d870 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts @@ -153,9 +153,7 @@ export class SQLQueryExecutionProcess extends Deferred { private onError(error: Error, status?: string) { // if task failed to execute during cancelling - it means it was cancelled successfully if (this.getState() === EDeferredState.CANCELLING) { - this.toCancelled(); - const message = `Query execution has been canceled${status ? `: ${status}` : ''}`; - this.notificationService.logException(error, 'Query execution Error', message); + this.toCancelled(error); } else { this.toRejected(error); } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts index f270511eea..4ee54dfc0b 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts @@ -10,7 +10,7 @@ import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { GraphQLService } from '@cloudbeaver/core-sdk'; -import { uuid, MetadataMap, isPromiseCancelledError } from '@cloudbeaver/core-utils'; +import { uuid, MetadataMap, EDeferredState } from '@cloudbeaver/core-utils'; import { DatabaseDataAccessMode, DataModelWrapper, fetchingSettings, TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer'; import type { @@ -146,8 +146,10 @@ export class SqlResultTabsService { ); } catch (exception) { // remove first panel if execution was cancelled - if (isPromiseCancelledError(exception) && isNewTabCreated) { + if (source.queryExecutionProcess?.getState() === EDeferredState.CANCELLED && isNewTabCreated) { this.removeGroup(editorState, tabGroup.groupId); + const message = `Query execution has been canceled${status ? `: ${status}` : ''}`; + this.notificationService.logException(exception, 'Query execution Error', message); } } }