Merge pull request #558 from dbeaver/feat/quoates

feat(core-sdk): CB-1403 add quota error
This commit is contained in:
Alexey
2021-11-26 15:06:22 +03:00
committed by GitHub
16 changed files with 145 additions and 126 deletions
@@ -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?: ServerErrorType;
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 as ServerErrorType;
this.stackTrace = error.stackTrace;
this.causedBy = error.causedBy;
}
@@ -7,11 +7,13 @@
*/
import { DetailsError } from './DetailsError';
import { ServerErrorType, ServerInternalError } from './ServerInternalError';
export interface IErrorDetails {
name: string;
message: string;
hasDetails: boolean;
errorType?: ServerErrorType;
}
export function hasDetails(error: Error): error is DetailsError {
@@ -20,9 +22,19 @@ 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;
if (error.errorType === ServerErrorType.QUOTE_EXCEEDED) {
details.hasDetails = false;
}
}
return details;
}
@@ -8,13 +8,6 @@ query exportDataFromContainer(
containerNodePath: $containerNodePath
parameters: $parameters
) {
id
running
taskResult
error {
message
errorCode
stackTrace
}
...AsyncTaskInfo
}
}
@@ -10,13 +10,6 @@ query exportDataFromResults(
resultsId: $resultsId
parameters: $parameters
) {
id
running
taskResult
error {
message
errorCode
stackTrace
}
...AsyncTaskInfo
}
}
@@ -0,0 +1,13 @@
fragment AsyncTaskInfo on AsyncTaskInfo {
id
name
running
status
error {
message
errorCode
errorType
stackTrace
}
taskResult
}
@@ -6,15 +6,6 @@ mutation getAsyncTaskInfo(
id: $taskId
removeOnFinish: $removeOnFinish
) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
@@ -15,15 +15,6 @@ mutation asyncReadDataFromContainer(
filter: $filter
dataFormat: $dataFormat
) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
@@ -14,15 +14,6 @@ mutation asyncSqlExecuteQuery(
filter: $filter
dataFormat: $dataFormat
) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
+33 -56
View File
@@ -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']>;
}
@@ -1634,7 +1635,7 @@ export type ExportDataFromContainerQueryVariables = Exact<{
parameters: DataTransferParameters;
}>;
export interface ExportDataFromContainerQuery { taskInfo: { id: string; running: boolean; taskResult?: Maybe<any>; error?: Maybe<{ message?: Maybe<string>; errorCode?: Maybe<string>; stackTrace?: Maybe<string> }> } }
export interface ExportDataFromContainerQuery { 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 ExportDataFromResultsQueryVariables = Exact<{
connectionId: Scalars['ID'];
@@ -1643,7 +1644,7 @@ export type ExportDataFromResultsQueryVariables = Exact<{
parameters: DataTransferParameters;
}>;
export interface ExportDataFromResultsQuery { taskInfo: { id: string; running: boolean; taskResult?: Maybe<any>; error?: Maybe<{ message?: Maybe<string>; errorCode?: Maybe<string>; stackTrace?: Maybe<string> }> } }
export interface ExportDataFromResultsQuery { 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 GetDataTransferProcessorsQueryVariables = Exact<{ [key: string]: never }>;
@@ -1668,6 +1669,8 @@ export interface AdminUserInfoFragment { userId: string; grantedRoles: string[];
export interface AllNavigatorSettingsFragment { showSystemObjects: boolean; showUtilityObjects: boolean; showOnlyEntities: boolean; mergeEntities: boolean; hideFolders: boolean; hideSchemas: boolean; hideVirtualModel: boolean }
export interface AsyncTaskInfoFragment { 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 interface AuthProviderConfigurationParametersFragment { id?: Maybe<string>; displayName?: Maybe<string>; description?: Maybe<string>; category?: Maybe<string>; dataType?: Maybe<string>; value?: Maybe<any>; validValues?: Maybe<Array<Maybe<any>>>; defaultValue?: Maybe<any>; length: ObjectPropertyLength; features: string[]; order: number }
export interface AuthTokenFragment { authProvider: string; authConfiguration?: Maybe<string>; loginTime: any; message?: Maybe<string>; origin: { type: string; subType?: Maybe<string>; displayName: string; icon?: Maybe<string>; details?: Maybe<Array<{ id?: Maybe<string>; displayName?: Maybe<string>; description?: Maybe<string>; category?: Maybe<string>; dataType?: Maybe<string>; defaultValue?: Maybe<any>; validValues?: Maybe<Array<Maybe<any>>>; value?: Maybe<any>; length: ObjectPropertyLength; features: string[]; order: number }>> } }
@@ -1695,7 +1698,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 }>;
@@ -1710,7 +1713,7 @@ export type AsyncReadDataFromContainerMutationVariables = Exact<{
dataFormat?: Maybe<ResultDataFormat>;
}>;
export interface AsyncReadDataFromContainerMutation { 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 AsyncReadDataFromContainerMutation { 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 AsyncSqlExecuteQueryMutationVariables = Exact<{
connectionId: Scalars['ID'];
@@ -1721,7 +1724,7 @@ export type AsyncSqlExecuteQueryMutationVariables = Exact<{
dataFormat?: Maybe<ResultDataFormat>;
}>;
export interface AsyncSqlExecuteQueryMutation { 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 AsyncSqlExecuteQueryMutation { 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 AsyncSqlExplainExecutionPlanMutationVariables = Exact<{
connectionId: Scalars['ID'];
@@ -1948,6 +1951,21 @@ export const AdminUserInfoFragmentDoc = `
}
}
${ObjectOriginInfoFragmentDoc}`;
export const AsyncTaskInfoFragmentDoc = `
fragment AsyncTaskInfo on AsyncTaskInfo {
id
name
running
status
error {
message
errorCode
errorType
stackTrace
}
taskResult
}
`;
export const AuthProviderConfigurationParametersFragmentDoc = `
fragment AuthProviderConfigurationParameters on ObjectPropertyInfo {
id
@@ -2672,17 +2690,10 @@ export const ExportDataFromContainerDocument = `
containerNodePath: $containerNodePath
parameters: $parameters
) {
id
running
taskResult
error {
message
errorCode
stackTrace
}
...AsyncTaskInfo
}
}
`;
${AsyncTaskInfoFragmentDoc}`;
export const ExportDataFromResultsDocument = `
query exportDataFromResults($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $parameters: DataTransferParameters!) {
taskInfo: dataTransferExportDataFromResults(
@@ -2691,17 +2702,10 @@ export const ExportDataFromResultsDocument = `
resultsId: $resultsId
parameters: $parameters
) {
id
running
taskResult
error {
message
errorCode
stackTrace
}
...AsyncTaskInfo
}
}
`;
${AsyncTaskInfoFragmentDoc}`;
export const GetDataTransferProcessorsDocument = `
query getDataTransferProcessors {
processors: dataTransferAvailableStreamProcessors {
@@ -2756,19 +2760,10 @@ export const NavGetStructContainersDocument = `
export const GetAsyncTaskInfoDocument = `
mutation getAsyncTaskInfo($taskId: String!, $removeOnFinish: Boolean!) {
taskInfo: asyncTaskInfo(id: $taskId, removeOnFinish: $removeOnFinish) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
`;
${AsyncTaskInfoFragmentDoc}`;
export const GetNetworkHandlersDocument = `
query getNetworkHandlers {
handlers: networkHandlers {
@@ -2794,19 +2789,10 @@ export const AsyncReadDataFromContainerDocument = `
filter: $filter
dataFormat: $dataFormat
) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
`;
${AsyncTaskInfoFragmentDoc}`;
export const AsyncSqlExecuteQueryDocument = `
mutation asyncSqlExecuteQuery($connectionId: ID!, $contextId: ID!, $query: String!, $resultId: ID, $filter: SQLDataFilter, $dataFormat: ResultDataFormat) {
taskInfo: asyncSqlExecuteQuery(
@@ -2817,19 +2803,10 @@ export const AsyncSqlExecuteQueryDocument = `
filter: $filter
dataFormat: $dataFormat
) {
id
name
running
status
error {
message
errorCode
stackTrace
}
taskResult
...AsyncTaskInfo
}
}
`;
${AsyncTaskInfoFragmentDoc}`;
export const AsyncSqlExplainExecutionPlanDocument = `
mutation asyncSqlExplainExecutionPlan($connectionId: ID!, $contextId: ID!, $query: String!, $configuration: Object!) {
taskInfo: asyncSqlExplainExecutionPlan(
@@ -21,6 +21,14 @@ import { EDeferredState } from '@cloudbeaver/core-utils';
import { ExportNotificationController } from './ExportNotificationController';
const styles = css`
message {
composes: theme-typography--body1 from global;
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"
@@ -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>
{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>
);
});