mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat(core-sdk): CB-1403 add quota error
This commit is contained in:
@@ -67,5 +67,6 @@ export const defaultENLocale = [
|
||||
['app_root_session_expired_title', 'Session expired'],
|
||||
['app_root_session_expired_message', 'Session has been expired. Do you want to reload?'],
|
||||
['app_root_session_expired_reload', 'Reload'],
|
||||
['app_root_quota_exceeded', 'Quota exceeded'],
|
||||
['core_eventsLog_dbeaverErrorDetails', 'Error details'],
|
||||
];
|
||||
|
||||
@@ -65,5 +65,6 @@ export const defaultRULocale = [
|
||||
['app_root_session_expired_title', 'Сессия истекла'],
|
||||
['app_root_session_expired_message', 'Сессия истекла. Хотите перезагрузить страницу?'],
|
||||
['app_root_session_expired_reload', 'Перезагрузить'],
|
||||
['app_root_quota_exceeded', 'Квота превышена'],
|
||||
['core_eventsLog_dbeaverErrorDetails', 'Детали ошибки'],
|
||||
];
|
||||
|
||||
@@ -9,8 +9,13 @@
|
||||
import { DetailsError } from './DetailsError';
|
||||
import type { ServerError } from './sdk';
|
||||
|
||||
export enum ServerErrorType {
|
||||
'QUOTE_EXCEEDED' = 'quotaExceeded'
|
||||
}
|
||||
|
||||
export class ServerInternalError extends DetailsError implements ServerError {
|
||||
readonly errorCode?: string;
|
||||
readonly errorType?: string;
|
||||
readonly stackTrace?: string;
|
||||
readonly causedBy?: ServerError;
|
||||
|
||||
@@ -18,6 +23,7 @@ export class ServerInternalError extends DetailsError implements ServerError {
|
||||
super(error.message);
|
||||
this.name = 'Server Internal Error';
|
||||
this.errorCode = error.errorCode;
|
||||
this.errorType = error.errorType;
|
||||
this.stackTrace = error.stackTrace;
|
||||
this.causedBy = error.causedBy;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
*/
|
||||
|
||||
import { DetailsError } from './DetailsError';
|
||||
import { ServerInternalError } from './ServerInternalError';
|
||||
|
||||
export interface IErrorDetails {
|
||||
name: string;
|
||||
message: string;
|
||||
hasDetails: boolean;
|
||||
errorType?: string;
|
||||
}
|
||||
|
||||
export function hasDetails(error: Error): error is DetailsError {
|
||||
@@ -20,9 +22,16 @@ export function hasDetails(error: Error): error is DetailsError {
|
||||
|
||||
export function getErrorDetails(error: Error | DetailsError): IErrorDetails {
|
||||
const exceptionMessage = hasDetails(error) ? error.errorMessage : error.message || error.name;
|
||||
return {
|
||||
|
||||
const details: IErrorDetails = {
|
||||
name: error.name,
|
||||
message: exceptionMessage,
|
||||
hasDetails: hasDetails(error),
|
||||
};
|
||||
|
||||
if (error instanceof ServerInternalError && error.errorType) {
|
||||
details.errorType = error.errorType;
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ mutation getAsyncTaskInfo(
|
||||
error {
|
||||
message
|
||||
errorCode
|
||||
errorType
|
||||
stackTrace
|
||||
}
|
||||
taskResult
|
||||
|
||||
@@ -1133,6 +1133,7 @@ export interface ServerConfigInput {
|
||||
export interface ServerError {
|
||||
causedBy?: Maybe<ServerError>;
|
||||
errorCode?: Maybe<Scalars['String']>;
|
||||
errorType?: Maybe<Scalars['String']>;
|
||||
message?: Maybe<Scalars['String']>;
|
||||
stackTrace?: Maybe<Scalars['String']>;
|
||||
}
|
||||
@@ -1695,7 +1696,7 @@ export type GetAsyncTaskInfoMutationVariables = Exact<{
|
||||
removeOnFinish: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
export interface GetAsyncTaskInfoMutation { taskInfo: { id: string; name?: Maybe<string>; running: boolean; status?: Maybe<string>; taskResult?: Maybe<any>; error?: Maybe<{ message?: Maybe<string>; errorCode?: Maybe<string>; stackTrace?: Maybe<string> }> } }
|
||||
export interface GetAsyncTaskInfoMutation { taskInfo: { id: string; name?: Maybe<string>; running: boolean; status?: Maybe<string>; taskResult?: Maybe<any>; error?: Maybe<{ message?: Maybe<string>; errorCode?: Maybe<string>; errorType?: Maybe<string>; stackTrace?: Maybe<string> }> } }
|
||||
|
||||
export type GetNetworkHandlersQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
@@ -2763,6 +2764,7 @@ export const GetAsyncTaskInfoDocument = `
|
||||
error {
|
||||
message
|
||||
errorCode
|
||||
errorType
|
||||
stackTrace
|
||||
}
|
||||
taskResult
|
||||
|
||||
@@ -21,6 +21,14 @@ import { EDeferredState } from '@cloudbeaver/core-utils';
|
||||
import { ExportNotificationController } from './ExportNotificationController';
|
||||
|
||||
const styles = css`
|
||||
message {
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
overflow: auto;
|
||||
max-height: 100px;
|
||||
word-break: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
source-name {
|
||||
composes: theme-typography--body2 from global;
|
||||
padding-top: 16px;
|
||||
@@ -44,14 +52,15 @@ export const ExportNotification = observer<Props>(function ExportNotification({
|
||||
}) {
|
||||
const controller = useController(ExportNotificationController, notification);
|
||||
const translate = useTranslate();
|
||||
const { title, status } = controller.status;
|
||||
const { title, status, message } = controller.status;
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<SnackbarWrapper unclosable={status === ENotificationType.Loading} onClose={controller.delete}>
|
||||
<SnackbarStatus status={status} />
|
||||
<SnackbarContent>
|
||||
<SnackbarBody title={translate(title)}>
|
||||
<source-name as="div">
|
||||
{message && <message>{message}</message>}
|
||||
<source-name>
|
||||
{controller.sourceName}
|
||||
{controller.task?.context.sourceName && (
|
||||
<pre title={controller.task.context.sourceName}>
|
||||
@@ -61,7 +70,7 @@ export const ExportNotification = observer<Props>(function ExportNotification({
|
||||
</source-name>
|
||||
</SnackbarBody>
|
||||
<SnackbarFooter timestamp={notification.timestamp}>
|
||||
{status === ENotificationType.Info && (
|
||||
{status === ENotificationType.Info && controller.downloadUrl && (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
+20
-3
@@ -14,6 +14,7 @@ import { CommonDialogService } from '@cloudbeaver/core-dialogs';
|
||||
import { ENotificationType, INotification } from '@cloudbeaver/core-events';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
|
||||
import { ServerInternalError, ServerErrorType } from '@cloudbeaver/core-sdk';
|
||||
import { Deferred, EDeferredState } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { DataExportProcessService, ExportProcess } from '../DataExportProcessService';
|
||||
@@ -21,6 +22,7 @@ import { DataExportProcessService, ExportProcess } from '../DataExportProcessSer
|
||||
interface ExportNotificationStatus {
|
||||
title: string;
|
||||
status: ENotificationType;
|
||||
message?: string;
|
||||
}
|
||||
@injectable()
|
||||
export class ExportNotificationController implements IInitializableController {
|
||||
@@ -67,8 +69,23 @@ export class ExportNotificationController implements IInitializableController {
|
||||
return { title: 'ui_processing_canceling', status: ENotificationType.Loading };
|
||||
case EDeferredState.RESOLVED:
|
||||
return { title: 'data_transfer_notification_ready', status: ENotificationType.Info };
|
||||
default:
|
||||
return { title: 'data_transfer_notification_error', status: ENotificationType.Error };
|
||||
case EDeferredState.CANCELLED:
|
||||
return { title: 'data_transfer_notification_cancelled', status: ENotificationType.Info };
|
||||
default: {
|
||||
const error = this.process?.getRejectionReason();
|
||||
|
||||
let title = 'data_transfer_notification_error';
|
||||
let status = ENotificationType.Error;
|
||||
let message = '';
|
||||
|
||||
if (error instanceof ServerInternalError && error.errorType === ServerErrorType.QUOTE_EXCEEDED) {
|
||||
title = 'app_root_quota_exceeded';
|
||||
status = ENotificationType.Info;
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
return { title, status, message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +125,7 @@ export class ExportNotificationController implements IInitializableController {
|
||||
this.dataExportProcessService.cancel(this.notification.extraProps.source);
|
||||
};
|
||||
|
||||
showDetails= async (): Promise<void> => {
|
||||
showDetails = async (): Promise<void> => {
|
||||
this.isDetailsDialogOpen = true;
|
||||
try {
|
||||
this.notification.showDetails();
|
||||
|
||||
@@ -6,7 +6,8 @@ export default [
|
||||
['data_transfer_dialog_preparation', 'We prepare your file for export. Please wait...'],
|
||||
['data_transfer_notification_preparation', 'We prepare your file for download. Please wait...'],
|
||||
['data_transfer_notification_ready', 'File is ready to download'],
|
||||
['data_transfer_notification_error', 'Preparation failed or canceled'],
|
||||
['data_transfer_notification_error', 'Data export failed'],
|
||||
['data_transfer_notification_cancelled', 'Data export was cancelled'],
|
||||
['data_transfer_notification_download', 'Download'],
|
||||
['data_transfer_notification_delete', 'Delete'],
|
||||
['data_transfer_exporting_table', 'Table:'],
|
||||
|
||||
@@ -6,7 +6,8 @@ export default [
|
||||
['data_transfer_dialog_preparation', 'Подготавливаем файл к экспорту. Пожалуйста подождите...'],
|
||||
['data_transfer_notification_preparation', 'Подготавливаем файл к загрузке. Пожалуйста подождите...'],
|
||||
['data_transfer_notification_ready', 'Файл готов к загрузке'],
|
||||
['data_transfer_notification_error', 'Подготовка файла к загрузке не удалась или была отменена'],
|
||||
['data_transfer_notification_error', 'Не удалось экспортировать данные'],
|
||||
['data_transfer_notification_cancelled', 'Экспорт данных был отменен'],
|
||||
['data_transfer_notification_download', 'Загрузить'],
|
||||
['data_transfer_notification_delete', 'Удалить'],
|
||||
['data_transfer_exporting_table', 'Таблица:'],
|
||||
|
||||
@@ -12,6 +12,7 @@ import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { Button, IconOrImage, useErrorDetails, useObservableRef, useStateDelay } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { ServerErrorType } from '@cloudbeaver/core-sdk';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
@@ -38,6 +39,7 @@ const style = composes(
|
||||
transition: opacity 0.3s ease-in-out, width 0.3s ease-in-out, height 0.3s ease-in-out, background 0.3s ease-in-out;
|
||||
|
||||
&[|animated] {
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -46,7 +48,14 @@ const style = composes(
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: transparent!important;
|
||||
overflow: hidden;
|
||||
|
||||
& IconOrImage {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
& error-message, & controls {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&[|errorHidden] {
|
||||
pointer-events: none;
|
||||
@@ -55,6 +64,7 @@ const style = composes(
|
||||
error-body {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
error-message {
|
||||
@@ -63,10 +73,13 @@ const style = composes(
|
||||
IconOrImage {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
Button {
|
||||
margin-right: 16px;
|
||||
controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
& > Button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
@@ -112,24 +125,33 @@ export const TableError = observer<Props>(function TableViewer({
|
||||
const animated = useStateDelay(!!errorInfo.error && !loading, 1);
|
||||
|
||||
const errorHidden = errorInfo.error === null;
|
||||
const quote = error.details?.errorType === ServerErrorType.QUOTE_EXCEEDED;
|
||||
|
||||
let icon = '/icons/error_icon.svg';
|
||||
|
||||
if (quote) {
|
||||
icon = '/icons/info_icon.svg';
|
||||
}
|
||||
|
||||
return styled(useStyles(style))(
|
||||
<error {...use({ animated, collapsed: !errorInfo.display, errorHidden })} className={className}>
|
||||
<error-body>
|
||||
<IconOrImage icon='/icons/error_icon.svg' onClick={() => errorInfo.show()} />
|
||||
<IconOrImage icon={icon} title={error.details?.message} onClick={() => errorInfo.show()} />
|
||||
<error-message>{error.details?.message}</error-message>
|
||||
</error-body>
|
||||
<Button type='button' mod={['outlined']} onClick={() => errorInfo.hide()}>
|
||||
{translate('ui_error_close')}
|
||||
</Button>
|
||||
{error.details?.hasDetails && (
|
||||
<Button type='button' mod={['outlined']} onClick={error.open}>
|
||||
{translate('ui_errors_details')}
|
||||
<controls>
|
||||
<Button type='button' mod={['outlined']} onClick={() => errorInfo.hide()}>
|
||||
{translate('ui_error_close')}
|
||||
</Button>
|
||||
)}
|
||||
<Button type='button' mod={['unelevated']} onClick={() => model.retry()}>
|
||||
{translate('ui_processing_retry')}
|
||||
</Button>
|
||||
{!quote && error.details?.hasDetails && (
|
||||
<Button type='button' mod={['outlined']} onClick={error.open}>
|
||||
{translate('ui_errors_details')}
|
||||
</Button>
|
||||
)}
|
||||
<Button type='button' mod={['unelevated']} onClick={() => model.retry()}>
|
||||
{translate('ui_processing_retry')}
|
||||
</Button>
|
||||
</controls>
|
||||
</error>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user