mirror of https://github.com/apache/superset.git
refactor: Move fetchTimeRange to core package (#27852)
This commit is contained in:
parent
30bc8f06dc
commit
a498d6d10f
|
@ -34,3 +34,8 @@ export const DEFAULT_FETCH_RETRY_OPTIONS: FetchRetryOptions = {
|
||||||
retryDelay: 1000,
|
retryDelay: 1000,
|
||||||
retryOn: [503],
|
retryOn: [503],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const COMMON_ERR_MESSAGES = {
|
||||||
|
SESSION_TIMED_OUT:
|
||||||
|
'Your session timed out, please refresh your page and try again.',
|
||||||
|
};
|
||||||
|
|
|
@ -22,4 +22,5 @@ export { default as SupersetClient } from './SupersetClient';
|
||||||
export { default as SupersetClientClass } from './SupersetClientClass';
|
export { default as SupersetClientClass } from './SupersetClientClass';
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './constants';
|
||||||
export { default as __hack_reexport_connection } from './types';
|
export { default as __hack_reexport_connection } from './types';
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { JsonObject, SupersetClientResponse, t } from '@superset-ui/core';
|
|
||||||
import {
|
import {
|
||||||
|
COMMON_ERR_MESSAGES,
|
||||||
|
JsonObject,
|
||||||
|
SupersetClientResponse,
|
||||||
|
t,
|
||||||
SupersetError,
|
SupersetError,
|
||||||
ErrorTypeEnum,
|
ErrorTypeEnum,
|
||||||
} from 'src/components/ErrorMessage/types';
|
} from '@superset-ui/core';
|
||||||
import COMMON_ERR_MESSAGES from './errorMessages';
|
|
||||||
|
|
||||||
// The response always contains an error attribute, can contain anything from the
|
// The response always contains an error attribute, can contain anything from the
|
||||||
// SupersetClientResponse object, and can contain a spread JSON blob
|
// SupersetClientResponse object, and can contain a spread JSON blob
|
||||||
|
@ -86,29 +88,6 @@ export function parseErrorJson(responseObject: JsonObject): ClientErrorObject {
|
||||||
return { ...error, error: error.error }; // explicit ClientErrorObject
|
return { ...error, error: error.error }; // explicit ClientErrorObject
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Utility to get standardized error text for generic update failures
|
|
||||||
*/
|
|
||||||
export async function getErrorText(
|
|
||||||
errorObject: ErrorType,
|
|
||||||
source: ErrorTextSource,
|
|
||||||
) {
|
|
||||||
const { error, message } = await getClientErrorObject(errorObject);
|
|
||||||
let errorText = t('Sorry, an unknown error occurred.');
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
errorText = t(
|
|
||||||
'Sorry, there was an error saving this %s: %s',
|
|
||||||
source,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (typeof message === 'string' && message === 'Forbidden') {
|
|
||||||
errorText = t('You do not have permission to edit this %s', source);
|
|
||||||
}
|
|
||||||
return errorText;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClientErrorObject(
|
export function getClientErrorObject(
|
||||||
response:
|
response:
|
||||||
| SupersetClientResponse
|
| SupersetClientResponse
|
||||||
|
@ -203,6 +182,29 @@ export function getClientErrorObject(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility to get standardized error text for generic update failures
|
||||||
|
*/
|
||||||
|
export async function getErrorText(
|
||||||
|
errorObject: ErrorType,
|
||||||
|
source: ErrorTextSource,
|
||||||
|
) {
|
||||||
|
const { error, message } = await getClientErrorObject(errorObject);
|
||||||
|
let errorText = t('Sorry, an unknown error occurred.');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
errorText = t(
|
||||||
|
'Sorry, there was an error saving this %s: %s',
|
||||||
|
source,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof message === 'string' && message === 'Forbidden') {
|
||||||
|
errorText = t('You do not have permission to edit this %s', source);
|
||||||
|
}
|
||||||
|
return errorText;
|
||||||
|
}
|
||||||
|
|
||||||
export function getClientErrorMessage(
|
export function getClientErrorMessage(
|
||||||
message: string,
|
message: string,
|
||||||
clientError?: ClientErrorObject,
|
clientError?: ClientErrorObject,
|
|
@ -31,6 +31,7 @@ export { default as normalizeOrderBy } from './normalizeOrderBy';
|
||||||
export { normalizeTimeColumn } from './normalizeTimeColumn';
|
export { normalizeTimeColumn } from './normalizeTimeColumn';
|
||||||
export { default as extractQueryFields } from './extractQueryFields';
|
export { default as extractQueryFields } from './extractQueryFields';
|
||||||
export * from './getXAxis';
|
export * from './getXAxis';
|
||||||
|
export * from './getClientErrorObject';
|
||||||
|
|
||||||
export * from './types/AnnotationLayer';
|
export * from './types/AnnotationLayer';
|
||||||
export * from './types/QueryFormData';
|
export * from './types/QueryFormData';
|
||||||
|
|
|
@ -166,6 +166,7 @@ export interface QueryContext {
|
||||||
form_data?: QueryFormData;
|
form_data?: QueryFormData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep in sync with superset/errors.py
|
||||||
export const ErrorTypeEnum = {
|
export const ErrorTypeEnum = {
|
||||||
// Frontend errors
|
// Frontend errors
|
||||||
FRONTEND_CSRF_ERROR: 'FRONTEND_CSRF_ERROR',
|
FRONTEND_CSRF_ERROR: 'FRONTEND_CSRF_ERROR',
|
||||||
|
@ -187,9 +188,10 @@ export const ErrorTypeEnum = {
|
||||||
CONNECTION_UNKNOWN_DATABASE_ERROR: 'CONNECTION_UNKNOWN_DATABASE_ERROR',
|
CONNECTION_UNKNOWN_DATABASE_ERROR: 'CONNECTION_UNKNOWN_DATABASE_ERROR',
|
||||||
CONNECTION_DATABASE_PERMISSIONS_ERROR:
|
CONNECTION_DATABASE_PERMISSIONS_ERROR:
|
||||||
'CONNECTION_DATABASE_PERMISSIONS_ERROR',
|
'CONNECTION_DATABASE_PERMISSIONS_ERROR',
|
||||||
CONNECTION_MISSING_PARAMETERS_ERRORS: 'CONNECTION_MISSING_PARAMETERS_ERRORS',
|
CONNECTION_MISSING_PARAMETERS_ERROR: 'CONNECTION_MISSING_PARAMETERS_ERROR',
|
||||||
OBJECT_DOES_NOT_EXIST_ERROR: 'OBJECT_DOES_NOT_EXIST_ERROR',
|
OBJECT_DOES_NOT_EXIST_ERROR: 'OBJECT_DOES_NOT_EXIST_ERROR',
|
||||||
SYNTAX_ERROR: 'SYNTAX_ERROR',
|
SYNTAX_ERROR: 'SYNTAX_ERROR',
|
||||||
|
CONNECTION_DATABASE_TIMEOUT: 'CONNECTION_DATABASE_TIMEOUT',
|
||||||
|
|
||||||
// Viz errors
|
// Viz errors
|
||||||
VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR',
|
VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR',
|
||||||
|
@ -203,12 +205,17 @@ export const ErrorTypeEnum = {
|
||||||
DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR',
|
DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR',
|
||||||
QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_ERROR',
|
QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_ERROR',
|
||||||
MISSING_OWNERSHIP_ERROR: 'MISSING_OWNERSHIP_ERROR',
|
MISSING_OWNERSHIP_ERROR: 'MISSING_OWNERSHIP_ERROR',
|
||||||
|
USER_ACTIVITY_SECURITY_ACCESS_ERROR: 'USER_ACTIVITY_SECURITY_ACCESS_ERROR',
|
||||||
|
DASHBOARD_SECURITY_ACCESS_ERROR: 'DASHBOARD_SECURITY_ACCESS_ERROR',
|
||||||
|
CHART_SECURITY_ACCESS_ERROR: 'CHART_SECURITY_ACCESS_ERROR',
|
||||||
|
OAUTH2_REDIRECT: 'OAUTH2_REDIRECT',
|
||||||
|
OAUTH2_REDIRECT_ERROR: 'OAUTH2_REDIRECT_ERROR',
|
||||||
|
|
||||||
// Other errors
|
// Other errors
|
||||||
BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR',
|
BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR',
|
||||||
DATABASE_NOT_FOUND_ERROR: 'DATABASE_NOT_FOUND_ERROR',
|
DATABASE_NOT_FOUND_ERROR: 'DATABASE_NOT_FOUND_ERROR',
|
||||||
|
|
||||||
// Sqllab error
|
// Sql Lab errors
|
||||||
MISSING_TEMPLATE_PARAMS_ERROR: 'MISSING_TEMPLATE_PARAMS_ERROR',
|
MISSING_TEMPLATE_PARAMS_ERROR: 'MISSING_TEMPLATE_PARAMS_ERROR',
|
||||||
INVALID_TEMPLATE_PARAMS_ERROR: 'INVALID_TEMPLATE_PARAMS_ERROR',
|
INVALID_TEMPLATE_PARAMS_ERROR: 'INVALID_TEMPLATE_PARAMS_ERROR',
|
||||||
RESULTS_BACKEND_NOT_CONFIGURED_ERROR: 'RESULTS_BACKEND_NOT_CONFIGURED_ERROR',
|
RESULTS_BACKEND_NOT_CONFIGURED_ERROR: 'RESULTS_BACKEND_NOT_CONFIGURED_ERROR',
|
||||||
|
@ -218,6 +225,8 @@ export const ErrorTypeEnum = {
|
||||||
SQLLAB_TIMEOUT_ERROR: 'SQLLAB_TIMEOUT_ERROR',
|
SQLLAB_TIMEOUT_ERROR: 'SQLLAB_TIMEOUT_ERROR',
|
||||||
RESULTS_BACKEND_ERROR: 'RESULTS_BACKEND_ERROR',
|
RESULTS_BACKEND_ERROR: 'RESULTS_BACKEND_ERROR',
|
||||||
ASYNC_WORKERS_ERROR: 'ASYNC_WORKERS_ERROR',
|
ASYNC_WORKERS_ERROR: 'ASYNC_WORKERS_ERROR',
|
||||||
|
ADHOC_SUBQUERY_NOT_ALLOWED_ERROR: 'ADHOC_SUBQUERY_NOT_ALLOWED_ERROR',
|
||||||
|
INVALID_SQL_ERROR: 'INVALID_SQL_ERROR',
|
||||||
|
|
||||||
// Generic errors
|
// Generic errors
|
||||||
GENERIC_COMMAND_ERROR: 'GENERIC_COMMAND_ERROR',
|
GENERIC_COMMAND_ERROR: 'GENERIC_COMMAND_ERROR',
|
||||||
|
@ -226,16 +235,20 @@ export const ErrorTypeEnum = {
|
||||||
// API errors
|
// API errors
|
||||||
INVALID_PAYLOAD_FORMAT_ERROR: 'INVALID_PAYLOAD_FORMAT_ERROR',
|
INVALID_PAYLOAD_FORMAT_ERROR: 'INVALID_PAYLOAD_FORMAT_ERROR',
|
||||||
INVALID_PAYLOAD_SCHEMA_ERROR: 'INVALID_PAYLOAD_SCHEMA_ERROR',
|
INVALID_PAYLOAD_SCHEMA_ERROR: 'INVALID_PAYLOAD_SCHEMA_ERROR',
|
||||||
|
MARSHMALLOW_ERROR: 'MARSHMALLOW_ERROR',
|
||||||
|
|
||||||
|
// Report errors
|
||||||
|
REPORT_NOTIFICATION_ERROR: 'REPORT_NOTIFICATION_ERROR',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type ValueOf<T> = T[keyof T];
|
type ValueOf<T> = T[keyof T];
|
||||||
|
|
||||||
export type ErrorType = ValueOf<typeof ErrorTypeEnum>;
|
export type ErrorType = ValueOf<typeof ErrorTypeEnum>;
|
||||||
|
|
||||||
// Keep in sync with superset/views/errors.py
|
// Keep in sync with superset/errors.py
|
||||||
export type ErrorLevel = 'info' | 'warning' | 'error';
|
export type ErrorLevel = 'info' | 'warning' | 'error';
|
||||||
|
|
||||||
export type ErrorSource = 'dashboard' | 'explore' | 'sqllab';
|
export type ErrorSource = 'dashboard' | 'explore' | 'sqllab' | 'crud';
|
||||||
|
|
||||||
export type SupersetError<ExtraType = Record<string, any> | null> = {
|
export type SupersetError<ExtraType = Record<string, any> | null> = {
|
||||||
error_type: ErrorType;
|
error_type: ErrorType;
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import rison from 'rison';
|
||||||
|
import { SupersetClient, getClientErrorObject } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export const SEPARATOR = ' : ';
|
||||||
|
|
||||||
|
export const buildTimeRangeString = (since: string, until: string): string =>
|
||||||
|
`${since}${SEPARATOR}${until}`;
|
||||||
|
|
||||||
|
const formatDateEndpoint = (dttm: string, isStart?: boolean): string =>
|
||||||
|
dttm.replace('T00:00:00', '') || (isStart ? '-∞' : '∞');
|
||||||
|
|
||||||
|
export const formatTimeRange = (
|
||||||
|
timeRange: string,
|
||||||
|
columnPlaceholder = 'col',
|
||||||
|
) => {
|
||||||
|
const splitDateRange = timeRange.split(SEPARATOR);
|
||||||
|
if (splitDateRange.length === 1) return timeRange;
|
||||||
|
return `${formatDateEndpoint(
|
||||||
|
splitDateRange[0],
|
||||||
|
true,
|
||||||
|
)} ≤ ${columnPlaceholder} < ${formatDateEndpoint(splitDateRange[1])}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchTimeRange = async (
|
||||||
|
timeRange: string,
|
||||||
|
columnPlaceholder = 'col',
|
||||||
|
) => {
|
||||||
|
const query = rison.encode_uri(timeRange);
|
||||||
|
const endpoint = `/api/v1/time_range/?q=${query}`;
|
||||||
|
try {
|
||||||
|
const response = await SupersetClient.get({ endpoint });
|
||||||
|
const timeRangeString = buildTimeRangeString(
|
||||||
|
response?.json?.result[0]?.since || '',
|
||||||
|
response?.json?.result[0]?.until || '',
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
value: formatTimeRange(timeRangeString, columnPlaceholder),
|
||||||
|
};
|
||||||
|
} catch (response) {
|
||||||
|
const clientError = await getClientErrorObject(response);
|
||||||
|
return {
|
||||||
|
error: clientError.message || clientError.error || response.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -21,3 +21,4 @@ export * from './types';
|
||||||
|
|
||||||
export { default as getComparisonInfo } from './getComparisonInfo';
|
export { default as getComparisonInfo } from './getComparisonInfo';
|
||||||
export { default as getComparisonFilters } from './getComparisonFilters';
|
export { default as getComparisonFilters } from './getComparisonFilters';
|
||||||
|
export { SEPARATOR, fetchTimeRange } from './fetchTimeRange';
|
||||||
|
|
|
@ -49,14 +49,13 @@ const mockLoadQueryData = jest.fn<Promise<unknown>, unknown[]>(
|
||||||
createArrayPromise,
|
createArrayPromise,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const actual = jest.requireActual('../../../src/chart/clients/ChartClient');
|
||||||
// ChartClient is now a mock
|
// ChartClient is now a mock
|
||||||
jest.mock('../../../src/chart/clients/ChartClient', () =>
|
jest.spyOn(actual, 'default').mockImplementation(() => ({
|
||||||
jest.fn().mockImplementation(() => ({
|
loadDatasource: mockLoadDatasource,
|
||||||
loadDatasource: mockLoadDatasource,
|
loadFormData: mockLoadFormData,
|
||||||
loadFormData: mockLoadFormData,
|
loadQueryData: mockLoadQueryData,
|
||||||
loadQueryData: mockLoadQueryData,
|
}));
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const ChartClientMock = ChartClient as jest.Mock<ChartClient>;
|
const ChartClientMock = ChartClient as jest.Mock<ChartClient>;
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,11 @@ import {
|
||||||
SharedLabelColor,
|
SharedLabelColor,
|
||||||
SharedLabelColorSource,
|
SharedLabelColorSource,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { getAnalogousColors } from '../../src/color/utils';
|
|
||||||
|
|
||||||
jest.mock('../../src/color/utils', () => ({
|
const actual = jest.requireActual('../../src/color/utils');
|
||||||
getAnalogousColors: jest
|
const getAnalogousColorsSpy = jest
|
||||||
.fn()
|
.spyOn(actual, 'getAnalogousColors')
|
||||||
.mockImplementation(() => ['red', 'green', 'blue']),
|
.mockImplementation(() => ['red', 'green', 'blue']);
|
||||||
}));
|
|
||||||
|
|
||||||
describe('SharedLabelColor', () => {
|
describe('SharedLabelColor', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -161,7 +159,7 @@ describe('SharedLabelColor', () => {
|
||||||
sharedLabelColor.updateColorMap('', 'testColors');
|
sharedLabelColor.updateColorMap('', 'testColors');
|
||||||
const colorMap = sharedLabelColor.getColorMap();
|
const colorMap = sharedLabelColor.getColorMap();
|
||||||
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
||||||
expect(getAnalogousColors).not.toBeCalled();
|
expect(getAnalogousColorsSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use analagous colors', () => {
|
it('should use analagous colors', () => {
|
||||||
|
@ -176,7 +174,7 @@ describe('SharedLabelColor', () => {
|
||||||
sharedLabelColor.updateColorMap('', 'testColors');
|
sharedLabelColor.updateColorMap('', 'testColors');
|
||||||
const colorMap = sharedLabelColor.getColorMap();
|
const colorMap = sharedLabelColor.getColorMap();
|
||||||
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
||||||
expect(getAnalogousColors).toBeCalled();
|
expect(getAnalogousColorsSpy).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
COMMON_ERR_MESSAGES,
|
||||||
|
getClientErrorMessage,
|
||||||
|
getClientErrorObject,
|
||||||
|
getErrorText,
|
||||||
|
parseErrorJson,
|
||||||
|
ErrorTypeEnum,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
|
it('Returns a Promise', () => {
|
||||||
|
const response = getClientErrorObject('error');
|
||||||
|
expect(response instanceof Promise).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Returns a Promise that resolves to an object with an error key', async () => {
|
||||||
|
const error = 'error';
|
||||||
|
|
||||||
|
const errorObj = await getClientErrorObject(error);
|
||||||
|
expect(errorObj).toMatchObject({ error });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles Response that can be parsed as json', async () => {
|
||||||
|
const jsonError = { something: 'something', error: 'Error message' };
|
||||||
|
const jsonErrorString = JSON.stringify(jsonError);
|
||||||
|
|
||||||
|
const errorObj = await getClientErrorObject(new Response(jsonErrorString));
|
||||||
|
expect(errorObj).toMatchObject(jsonError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles backwards compatibility between old error messages and the new SIP-40 errors format', async () => {
|
||||||
|
const jsonError = {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
error_type: ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
|
||||||
|
extra: { engine: 'presto', link: 'https://www.google.com' },
|
||||||
|
level: 'error',
|
||||||
|
message: 'presto error: test error',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const jsonErrorString = JSON.stringify(jsonError);
|
||||||
|
|
||||||
|
const errorObj = await getClientErrorObject(new Response(jsonErrorString));
|
||||||
|
expect(errorObj.error).toEqual(jsonError.errors[0].message);
|
||||||
|
expect(errorObj.link).toEqual(jsonError.errors[0].extra.link);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles Response that can be parsed as text', async () => {
|
||||||
|
const textError = 'Hello I am a text error';
|
||||||
|
|
||||||
|
const errorObj = await getClientErrorObject(new Response(textError));
|
||||||
|
expect(errorObj).toMatchObject({ error: textError });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles TypeError Response', async () => {
|
||||||
|
const error = new TypeError('Failed to fetch');
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const errorObj = await getClientErrorObject(error);
|
||||||
|
expect(errorObj).toMatchObject({ error: 'Network error' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles timeout error', async () => {
|
||||||
|
const errorObj = await getClientErrorObject({
|
||||||
|
timeout: 1000,
|
||||||
|
statusText: 'timeout',
|
||||||
|
});
|
||||||
|
expect(errorObj).toMatchObject({
|
||||||
|
timeout: 1000,
|
||||||
|
statusText: 'timeout',
|
||||||
|
error: 'Request timed out',
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
error_type: ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR,
|
||||||
|
extra: {
|
||||||
|
timeout: 1,
|
||||||
|
issue_codes: [
|
||||||
|
{
|
||||||
|
code: 1000,
|
||||||
|
message: 'Issue 1000 - The dataset is too large to query.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 1001,
|
||||||
|
message: 'Issue 1001 - The database is under an unusual load.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
level: 'error',
|
||||||
|
message: 'Request timed out',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles plain text as input', async () => {
|
||||||
|
const error = 'error';
|
||||||
|
|
||||||
|
const errorObj = await getClientErrorObject(error);
|
||||||
|
expect(errorObj).toMatchObject({ error });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handles error with status text and message', async () => {
|
||||||
|
const statusText = 'status';
|
||||||
|
const message = 'message';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expect(await getClientErrorObject({ statusText, message })).toMatchObject({
|
||||||
|
error: statusText,
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
expect(await getClientErrorObject({ message })).toMatchObject({
|
||||||
|
error: message,
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
expect(await getClientErrorObject({})).toMatchObject({
|
||||||
|
error: 'An error occurred',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getClientErrorMessage', () => {
|
||||||
|
expect(getClientErrorMessage('error')).toEqual('error');
|
||||||
|
expect(
|
||||||
|
getClientErrorMessage('error', {
|
||||||
|
error: 'client error',
|
||||||
|
message: 'client error message',
|
||||||
|
}),
|
||||||
|
).toEqual('error:\nclient error message');
|
||||||
|
expect(
|
||||||
|
getClientErrorMessage('error', {
|
||||||
|
error: 'client error',
|
||||||
|
}),
|
||||||
|
).toEqual('error:\nclient error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseErrorJson with message', () => {
|
||||||
|
expect(parseErrorJson({ message: 'error message' })).toEqual({
|
||||||
|
message: 'error message',
|
||||||
|
error: 'error message',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parseErrorJson({
|
||||||
|
message: {
|
||||||
|
key1: ['error message1', 'error message2'],
|
||||||
|
key2: ['error message3', 'error message4'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
message: {
|
||||||
|
key1: ['error message1', 'error message2'],
|
||||||
|
key2: ['error message3', 'error message4'],
|
||||||
|
},
|
||||||
|
error: 'error message1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parseErrorJson({
|
||||||
|
message: {},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
message: {},
|
||||||
|
error: 'Invalid input',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseErrorJson with stacktrace', () => {
|
||||||
|
expect(
|
||||||
|
parseErrorJson({ error: 'error message', stack: 'stacktrace' }),
|
||||||
|
).toEqual({
|
||||||
|
error: 'Unexpected error: (no description, click to see stack trace)',
|
||||||
|
stacktrace: 'stacktrace',
|
||||||
|
stack: 'stacktrace',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parseErrorJson({
|
||||||
|
error: 'error message',
|
||||||
|
description: 'error description',
|
||||||
|
stack: 'stacktrace',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
error: 'Unexpected error: error description',
|
||||||
|
stacktrace: 'stacktrace',
|
||||||
|
description: 'error description',
|
||||||
|
stack: 'stacktrace',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseErrorJson with CSRF', () => {
|
||||||
|
expect(
|
||||||
|
parseErrorJson({
|
||||||
|
responseText: 'CSRF',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
error: COMMON_ERR_MESSAGES.SESSION_TIMED_OUT,
|
||||||
|
responseText: 'CSRF',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getErrorText', async () => {
|
||||||
|
expect(await getErrorText('error', 'dashboard')).toEqual(
|
||||||
|
'Sorry, there was an error saving this dashboard: error',
|
||||||
|
);
|
||||||
|
|
||||||
|
const error = JSON.stringify({ message: 'Forbidden' });
|
||||||
|
expect(await getErrorText(new Response(error), 'dashboard')).toEqual(
|
||||||
|
'You do not have permission to edit this dashboard',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await getErrorText(
|
||||||
|
new Response(JSON.stringify({ status: 'error' })),
|
||||||
|
'dashboard',
|
||||||
|
),
|
||||||
|
).toEqual('Sorry, an unknown error occurred.');
|
||||||
|
});
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
import { fetchTimeRange } from '@superset-ui/core';
|
||||||
|
import {
|
||||||
|
buildTimeRangeString,
|
||||||
|
formatTimeRange,
|
||||||
|
} from '../../src/time-comparison/fetchTimeRange';
|
||||||
|
|
||||||
|
afterEach(fetchMock.restore);
|
||||||
|
|
||||||
|
it('generates proper time range string', () => {
|
||||||
|
expect(
|
||||||
|
buildTimeRangeString('2010-07-30T00:00:00', '2020-07-30T00:00:00'),
|
||||||
|
).toBe('2010-07-30T00:00:00 : 2020-07-30T00:00:00');
|
||||||
|
expect(buildTimeRangeString('', '2020-07-30T00:00:00')).toBe(
|
||||||
|
' : 2020-07-30T00:00:00',
|
||||||
|
);
|
||||||
|
expect(buildTimeRangeString('', '')).toBe(' : ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a readable time range', () => {
|
||||||
|
expect(formatTimeRange('Last 7 days')).toBe('Last 7 days');
|
||||||
|
expect(formatTimeRange('No filter')).toBe('No filter');
|
||||||
|
expect(formatTimeRange('Yesterday : Tomorrow')).toBe(
|
||||||
|
'Yesterday ≤ col < Tomorrow',
|
||||||
|
);
|
||||||
|
expect(formatTimeRange('2010-07-30T00:00:00 : 2020-07-30T00:00:00')).toBe(
|
||||||
|
'2010-07-30 ≤ col < 2020-07-30',
|
||||||
|
);
|
||||||
|
expect(formatTimeRange('2010-07-30T01:00:00 : ')).toBe(
|
||||||
|
'2010-07-30T01:00:00 ≤ col < ∞',
|
||||||
|
);
|
||||||
|
expect(formatTimeRange(' : 2020-07-30T00:00:00')).toBe(
|
||||||
|
'-∞ ≤ col < 2020-07-30',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a formatted time range from response', async () => {
|
||||||
|
fetchMock.get("glob:*/api/v1/time_range/?q='Last+day'", {
|
||||||
|
result: [
|
||||||
|
{
|
||||||
|
since: '2021-04-13T00:00:00',
|
||||||
|
until: '2021-04-14T00:00:00',
|
||||||
|
timeRange: 'Last day',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeRange = await fetchTimeRange('Last day', 'temporal_col');
|
||||||
|
expect(timeRange).toEqual({
|
||||||
|
value: '2021-04-13 ≤ temporal_col < 2021-04-14',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a formatted time range from empty response', async () => {
|
||||||
|
fetchMock.get("glob:*/api/v1/time_range/?q='Last+day'", {
|
||||||
|
result: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeRange = await fetchTimeRange('Last day');
|
||||||
|
expect(timeRange).toEqual({
|
||||||
|
value: '-∞ ≤ col < ∞',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a formatted error message from response', async () => {
|
||||||
|
fetchMock.getOnce("glob:*/api/v1/time_range/?q='Last+day'", {
|
||||||
|
throws: new Response(JSON.stringify({ message: 'Network error' })),
|
||||||
|
});
|
||||||
|
let timeRange = await fetchTimeRange('Last day');
|
||||||
|
expect(timeRange).toEqual({
|
||||||
|
error: 'Network error',
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock.getOnce(
|
||||||
|
"glob:*/api/v1/time_range/?q='Last+day'",
|
||||||
|
{
|
||||||
|
throws: new Error('Internal Server Error'),
|
||||||
|
},
|
||||||
|
{ overwriteRoutes: true },
|
||||||
|
);
|
||||||
|
timeRange = await fetchTimeRange('Last day');
|
||||||
|
expect(timeRange).toEqual({
|
||||||
|
error: 'Internal Server Error',
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchMock.getOnce(
|
||||||
|
"glob:*/api/v1/time_range/?q='Last+day'",
|
||||||
|
{
|
||||||
|
throws: new Response(JSON.stringify({ statusText: 'Network error' }), {
|
||||||
|
statusText: 'Network error',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ overwriteRoutes: true },
|
||||||
|
);
|
||||||
|
timeRange = await fetchTimeRange('Last day');
|
||||||
|
expect(timeRange).toEqual({
|
||||||
|
error: 'Network error',
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,6 +23,8 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
COMMON_ERR_MESSAGES,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { invert, mapKeys } from 'lodash';
|
import { invert, mapKeys } from 'lodash';
|
||||||
|
|
||||||
|
@ -33,8 +35,6 @@ import {
|
||||||
addSuccessToast as addSuccessToastAction,
|
addSuccessToast as addSuccessToastAction,
|
||||||
addWarningToast as addWarningToastAction,
|
addWarningToast as addWarningToastAction,
|
||||||
} from 'src/components/MessageToasts/actions';
|
} from 'src/components/MessageToasts/actions';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
|
|
||||||
import { LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY } from 'src/logger/LogUtils';
|
import { LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY } from 'src/logger/LogUtils';
|
||||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||||
import { logEvent } from 'src/logger/actions';
|
import { logEvent } from 'src/logger/actions';
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
*/
|
*/
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
createWrapper,
|
createWrapper,
|
||||||
defaultStore as store,
|
defaultStore as store,
|
||||||
} from 'spec/helpers/testing-library';
|
} from 'spec/helpers/testing-library';
|
||||||
import { api } from 'src/hooks/apiResources/queryApi';
|
import { api } from 'src/hooks/apiResources/queryApi';
|
||||||
import { initialState } from 'src/SqlLab/fixtures';
|
import { initialState } from 'src/SqlLab/fixtures';
|
||||||
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
|
|
||||||
import { useAnnotations } from './useAnnotations';
|
import { useAnnotations } from './useAnnotations';
|
||||||
|
|
||||||
const fakeApiResult = {
|
const fakeApiResult = {
|
||||||
|
|
|
@ -18,16 +18,14 @@
|
||||||
*/
|
*/
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { COMMON_ERR_MESSAGES, ClientErrorObject, t } from '@superset-ui/core';
|
||||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||||
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
|
|
||||||
import { VALIDATION_DEBOUNCE_MS } from 'src/SqlLab/constants';
|
import { VALIDATION_DEBOUNCE_MS } from 'src/SqlLab/constants';
|
||||||
import {
|
import {
|
||||||
FetchValidationQueryParams,
|
FetchValidationQueryParams,
|
||||||
useQueryValidationsQuery,
|
useQueryValidationsQuery,
|
||||||
} from 'src/hooks/apiResources';
|
} from 'src/hooks/apiResources';
|
||||||
import { useDebounceValue } from 'src/hooks/useDebounceValue';
|
import { useDebounceValue } from 'src/hooks/useDebounceValue';
|
||||||
import { ClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { t } from '@superset-ui/core';
|
|
||||||
|
|
||||||
export function useAnnotations(params: FetchValidationQueryParams) {
|
export function useAnnotations(params: FetchValidationQueryParams) {
|
||||||
const { sql, dbId, schema, templateParams } = params;
|
const { sql, dbId, schema, templateParams } = params;
|
||||||
|
|
|
@ -23,13 +23,13 @@ import {
|
||||||
t,
|
t,
|
||||||
useTheme,
|
useTheme,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||||
import { storeQuery } from 'src/utils/common';
|
import { storeQuery } from 'src/utils/common';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||||
|
|
||||||
interface ShareSqlLabQueryProps {
|
interface ShareSqlLabQueryProps {
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { SupersetError } from '@superset-ui/core';
|
||||||
import { useChartOwnerNames } from 'src/hooks/apiResources';
|
import { useChartOwnerNames } from 'src/hooks/apiResources';
|
||||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chartId: string;
|
chartId: string;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { getControlsState } from 'src/explore/store';
|
import { getControlsState } from 'src/explore/store';
|
||||||
import {
|
import {
|
||||||
|
@ -38,7 +39,6 @@ import {
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import { logEvent } from 'src/logger/actions';
|
import { logEvent } from 'src/logger/actions';
|
||||||
import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
|
import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
|
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
|
||||||
import { updateDataMask } from 'src/dataMask/actions';
|
import { updateDataMask } from 'src/dataMask/actions';
|
||||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||||
|
|
|
@ -24,7 +24,12 @@ import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import Alert from 'src/components/Alert';
|
import Alert from 'src/components/Alert';
|
||||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
import {
|
||||||
|
SupersetClient,
|
||||||
|
t,
|
||||||
|
styled,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import TableView, { EmptyWrapperType } from 'src/components/TableView';
|
import TableView, { EmptyWrapperType } from 'src/components/TableView';
|
||||||
import { ServerPagination, SortByType } from 'src/components/TableView/types';
|
import { ServerPagination, SortByType } from 'src/components/TableView/types';
|
||||||
import StyledModal from 'src/components/Modal';
|
import StyledModal from 'src/components/Modal';
|
||||||
|
@ -33,7 +38,6 @@ import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||||
import Dataset from 'src/types/Dataset';
|
import Dataset from 'src/types/Dataset';
|
||||||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import { AntdInput } from 'src/components';
|
import { AntdInput } from 'src/components';
|
||||||
import { Input } from 'src/components/Input';
|
import { Input } from 'src/components/Input';
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
withTheme,
|
withTheme,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { Select, AsyncSelect, Row, Col } from 'src/components';
|
import { Select, AsyncSelect, Row, Col } from 'src/components';
|
||||||
import { FormLabel } from 'src/components/Form';
|
import { FormLabel } from 'src/components/Form';
|
||||||
|
@ -46,7 +47,6 @@ import Label from 'src/components/Label';
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import TableSelector from 'src/components/TableSelector';
|
import TableSelector from 'src/components/TableSelector';
|
||||||
import EditableTitle from 'src/components/EditableTitle';
|
import EditableTitle from 'src/components/EditableTitle';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
||||||
import TextControl from 'src/explore/components/controls/TextControl';
|
import TextControl from 'src/explore/components/controls/TextControl';
|
||||||
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
|
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
|
||||||
|
|
|
@ -26,16 +26,16 @@ import {
|
||||||
Metric,
|
Metric,
|
||||||
styled,
|
styled,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
|
getClientErrorObject,
|
||||||
t,
|
t,
|
||||||
|
SupersetError,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
import Modal from 'src/components/Modal';
|
import Modal from 'src/components/Modal';
|
||||||
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
|
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
|
||||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
|
||||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
const DatasourceEditor = AsyncEsmComponent(() => import('./DatasourceEditor'));
|
const DatasourceEditor = AsyncEsmComponent(() => import('./DatasourceEditor'));
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,8 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { supersetTheme } from '@superset-ui/core';
|
import { ErrorLevel, supersetTheme } from '@superset-ui/core';
|
||||||
import BasicErrorAlert from './BasicErrorAlert';
|
import BasicErrorAlert from './BasicErrorAlert';
|
||||||
import { ErrorLevel } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -17,9 +17,8 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { styled, useTheme } from '@superset-ui/core';
|
import { ErrorLevel, styled, useTheme } from '@superset-ui/core';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { ErrorLevel } from './types';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ level: ErrorLevel }>`
|
const StyledContainer = styled.div<{ level: ErrorLevel }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import DatabaseErrorMessage from './DatabaseErrorMessage';
|
import DatabaseErrorMessage from './DatabaseErrorMessage';
|
||||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import DatasetNotFoundErrorMessage from './DatasetNotFoundErrorMessage';
|
import DatasetNotFoundErrorMessage from './DatasetNotFoundErrorMessage';
|
||||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -20,10 +20,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { supersetTheme } from '@superset-ui/core';
|
import { ErrorLevel, ErrorSource, supersetTheme } from '@superset-ui/core';
|
||||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||||
import ErrorAlert from './ErrorAlert';
|
import ErrorAlert from './ErrorAlert';
|
||||||
import { ErrorLevel, ErrorSource } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -17,14 +17,19 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useState, ReactNode } from 'react';
|
import React, { useState, ReactNode } from 'react';
|
||||||
import { styled, useTheme, t } from '@superset-ui/core';
|
import {
|
||||||
|
ErrorLevel,
|
||||||
|
ErrorSource,
|
||||||
|
styled,
|
||||||
|
useTheme,
|
||||||
|
t,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { noOp } from 'src/utils/common';
|
import { noOp } from 'src/utils/common';
|
||||||
import Modal from 'src/components/Modal';
|
import Modal from 'src/components/Modal';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||||
|
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { ErrorLevel, ErrorSource } from './types';
|
|
||||||
import CopyToClipboard from '../CopyToClipboard';
|
import CopyToClipboard from '../CopyToClipboard';
|
||||||
|
|
||||||
const ErrorAlertDiv = styled.div<{ level: ErrorLevel }>`
|
const ErrorAlertDiv = styled.div<{ level: ErrorLevel }>`
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace';
|
import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace';
|
||||||
import BasicErrorAlert from './BasicErrorAlert';
|
import BasicErrorAlert from './BasicErrorAlert';
|
||||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -17,9 +17,8 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { t } from '@superset-ui/core';
|
import { ErrorSource, t, SupersetError } from '@superset-ui/core';
|
||||||
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
|
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
|
||||||
import { SupersetError, ErrorSource } from './types';
|
|
||||||
import ErrorAlert from './ErrorAlert';
|
import ErrorAlert from './ErrorAlert';
|
||||||
|
|
||||||
const DEFAULT_TITLE = t('Unexpected error');
|
const DEFAULT_TITLE = t('Unexpected error');
|
||||||
|
|
|
@ -20,8 +20,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
import {
|
||||||
import { ErrorLevel, ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
ErrorLevel,
|
||||||
|
ErrorTypeEnum,
|
||||||
|
ThemeProvider,
|
||||||
|
supersetTheme,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import MarshmallowErrorMessage from './MarshmallowErrorMessage';
|
import MarshmallowErrorMessage from './MarshmallowErrorMessage';
|
||||||
|
|
||||||
describe('MarshmallowErrorMessage', () => {
|
describe('MarshmallowErrorMessage', () => {
|
||||||
|
|
|
@ -23,13 +23,14 @@ import { Provider } from 'react-redux';
|
||||||
import { createStore } from 'redux';
|
import { createStore } from 'redux';
|
||||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
|
||||||
import OAuth2RedirectMessage from 'src/components/ErrorMessage/OAuth2RedirectMessage';
|
|
||||||
import {
|
import {
|
||||||
ErrorLevel,
|
ErrorLevel,
|
||||||
ErrorSource,
|
ErrorSource,
|
||||||
ErrorTypeEnum,
|
ErrorTypeEnum,
|
||||||
} from 'src/components/ErrorMessage/types';
|
ThemeProvider,
|
||||||
|
supersetTheme,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import OAuth2RedirectMessage from 'src/components/ErrorMessage/OAuth2RedirectMessage';
|
||||||
import { reRunQuery } from 'src/SqlLab/actions/sqlLab';
|
import { reRunQuery } from 'src/SqlLab/actions/sqlLab';
|
||||||
import { triggerQuery } from 'src/components/Chart/chartAction';
|
import { triggerQuery } from 'src/components/Chart/chartAction';
|
||||||
import { onRefresh } from 'src/dashboard/actions/dashboardState';
|
import { onRefresh } from 'src/dashboard/actions/dashboardState';
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import ParameterErrorMessage from './ParameterErrorMessage';
|
import ParameterErrorMessage from './ParameterErrorMessage';
|
||||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
|
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ErrorSource, ErrorTypeEnum, ErrorLevel } from '@superset-ui/core';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import TimeoutErrorMessage from './TimeoutErrorMessage';
|
import TimeoutErrorMessage from './TimeoutErrorMessage';
|
||||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/components/Icons/Icon',
|
'src/components/Icons/Icon',
|
||||||
|
|
|
@ -17,95 +17,14 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Keep in sync with superset/views/errors.py
|
import { ReactNode, ComponentType } from 'react';
|
||||||
export const ErrorTypeEnum = {
|
import { ErrorSource, SupersetError } from '@superset-ui/core';
|
||||||
// Frontend errors
|
|
||||||
FRONTEND_CSRF_ERROR: 'FRONTEND_CSRF_ERROR',
|
|
||||||
FRONTEND_NETWORK_ERROR: 'FRONTEND_NETWORK_ERROR',
|
|
||||||
FRONTEND_TIMEOUT_ERROR: 'FRONTEND_TIMEOUT_ERROR',
|
|
||||||
|
|
||||||
// DB Engine errors
|
|
||||||
GENERIC_DB_ENGINE_ERROR: 'GENERIC_DB_ENGINE_ERROR',
|
|
||||||
COLUMN_DOES_NOT_EXIST_ERROR: 'COLUMN_DOES_NOT_EXIST_ERROR',
|
|
||||||
TABLE_DOES_NOT_EXIST_ERROR: 'TABLE_DOES_NOT_EXIST_ERROR',
|
|
||||||
SCHEMA_DOES_NOT_EXIST_ERROR: 'SCHEMA_DOES_NOT_EXIST_ERROR',
|
|
||||||
CONNECTION_INVALID_USERNAME_ERROR: 'CONNECTION_INVALID_USERNAME_ERROR',
|
|
||||||
CONNECTION_INVALID_PASSWORD_ERROR: 'CONNECTION_INVALID_PASSWORD_ERROR',
|
|
||||||
CONNECTION_INVALID_HOSTNAME_ERROR: 'CONNECTION_INVALID_HOSTNAME_ERROR',
|
|
||||||
CONNECTION_PORT_CLOSED_ERROR: 'CONNECTION_PORT_CLOSED_ERROR',
|
|
||||||
CONNECTION_INVALID_PORT_ERROR: 'CONNECTION_INVALID_PORT_ERROR',
|
|
||||||
CONNECTION_HOST_DOWN_ERROR: 'CONNECTION_HOST_DOWN_ERROR',
|
|
||||||
CONNECTION_ACCESS_DENIED_ERROR: 'CONNECTION_ACCESS_DENIED_ERROR',
|
|
||||||
CONNECTION_UNKNOWN_DATABASE_ERROR: 'CONNECTION_UNKNOWN_DATABASE_ERROR',
|
|
||||||
CONNECTION_DATABASE_PERMISSIONS_ERROR:
|
|
||||||
'CONNECTION_DATABASE_PERMISSIONS_ERROR',
|
|
||||||
CONNECTION_MISSING_PARAMETERS_ERRORS: 'CONNECTION_MISSING_PARAMETERS_ERRORS',
|
|
||||||
OBJECT_DOES_NOT_EXIST_ERROR: 'OBJECT_DOES_NOT_EXIST_ERROR',
|
|
||||||
SYNTAX_ERROR: 'SYNTAX_ERROR',
|
|
||||||
|
|
||||||
// Viz errors
|
|
||||||
VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR',
|
|
||||||
UNKNOWN_DATASOURCE_TYPE_ERROR: 'UNKNOWN_DATASOURCE_TYPE_ERROR',
|
|
||||||
FAILED_FETCHING_DATASOURCE_INFO_ERROR:
|
|
||||||
'FAILED_FETCHING_DATASOURCE_INFO_ERROR',
|
|
||||||
|
|
||||||
// Security access errors
|
|
||||||
TABLE_SECURITY_ACCESS_ERROR: 'TABLE_SECURITY_ACCESS_ERROR',
|
|
||||||
DATASOURCE_SECURITY_ACCESS_ERROR: 'DATASOURCE_SECURITY_ACCESS_ERROR',
|
|
||||||
DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR',
|
|
||||||
QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_ERROR',
|
|
||||||
MISSING_OWNERSHIP_ERROR: 'MISSING_OWNERSHIP_ERROR',
|
|
||||||
DASHBOARD_SECURITY_ACCESS_ERROR: 'DASHBOARD_SECURITY_ACCESS_ERROR',
|
|
||||||
OAUTH2_REDIRECT: 'OAUTH2_REDIRECT',
|
|
||||||
OAUTH2_REDIRECT_ERROR: 'OAUTH2_REDIRECT_ERROR',
|
|
||||||
|
|
||||||
// Other errors
|
|
||||||
BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR',
|
|
||||||
DATABASE_NOT_FOUND_ERROR: 'DATABASE_NOT_FOUND_ERROR',
|
|
||||||
|
|
||||||
// Sqllab error
|
|
||||||
MISSING_TEMPLATE_PARAMS_ERROR: 'MISSING_TEMPLATE_PARAMS_ERROR',
|
|
||||||
INVALID_TEMPLATE_PARAMS_ERROR: 'INVALID_TEMPLATE_PARAMS_ERROR',
|
|
||||||
RESULTS_BACKEND_NOT_CONFIGURED_ERROR: 'RESULTS_BACKEND_NOT_CONFIGURED_ERROR',
|
|
||||||
DML_NOT_ALLOWED_ERROR: 'DML_NOT_ALLOWED_ERROR',
|
|
||||||
INVALID_CTAS_QUERY_ERROR: 'INVALID_CTAS_QUERY_ERROR',
|
|
||||||
INVALID_CVAS_QUERY_ERROR: 'INVALID_CVAS_QUERY_ERROR',
|
|
||||||
SQLLAB_TIMEOUT_ERROR: 'SQLLAB_TIMEOUT_ERROR',
|
|
||||||
RESULTS_BACKEND_ERROR: 'RESULTS_BACKEND_ERROR',
|
|
||||||
ASYNC_WORKERS_ERROR: 'ASYNC_WORKERS_ERROR',
|
|
||||||
|
|
||||||
// Generic errors
|
|
||||||
GENERIC_COMMAND_ERROR: 'GENERIC_COMMAND_ERROR',
|
|
||||||
GENERIC_BACKEND_ERROR: 'GENERIC_BACKEND_ERROR',
|
|
||||||
|
|
||||||
// API errors
|
|
||||||
INVALID_PAYLOAD_FORMAT_ERROR: 'INVALID_PAYLOAD_FORMAT_ERROR',
|
|
||||||
INVALID_PAYLOAD_SCHEMA_ERROR: 'INVALID_PAYLOAD_SCHEMA_ERROR',
|
|
||||||
MARSHMALLOW_ERROR: 'MARSHMALLOW_ERROR',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
type ValueOf<T> = T[keyof T];
|
|
||||||
|
|
||||||
export type ErrorType = ValueOf<typeof ErrorTypeEnum>;
|
|
||||||
|
|
||||||
// Keep in sync with superset/views/errors.py
|
|
||||||
export type ErrorLevel = 'info' | 'warning' | 'error';
|
|
||||||
|
|
||||||
export type ErrorSource = 'dashboard' | 'explore' | 'sqllab' | 'crud';
|
|
||||||
|
|
||||||
export type SupersetError<ExtraType = Record<string, any> | null> = {
|
|
||||||
error_type: ErrorType;
|
|
||||||
extra: ExtraType;
|
|
||||||
level: ErrorLevel;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ErrorMessageComponentProps<ExtraType = Record<string, any> | null> =
|
export type ErrorMessageComponentProps<ExtraType = Record<string, any> | null> =
|
||||||
{
|
{
|
||||||
error: SupersetError<ExtraType>;
|
error: SupersetError<ExtraType>;
|
||||||
source?: ErrorSource;
|
source?: ErrorSource;
|
||||||
subtitle?: React.ReactNode;
|
subtitle?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ErrorMessageComponent =
|
export type ErrorMessageComponent = ComponentType<ErrorMessageComponentProps>;
|
||||||
React.ComponentType<ErrorMessageComponentProps>;
|
|
||||||
|
|
|
@ -29,11 +29,15 @@ import React, {
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
ClipboardEvent,
|
ClipboardEvent,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { ensureIsArray, t, usePrevious } from '@superset-ui/core';
|
import {
|
||||||
|
ensureIsArray,
|
||||||
|
t,
|
||||||
|
usePrevious,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { LabeledValue as AntdLabeledValue } from 'antd/lib/select';
|
import { LabeledValue as AntdLabeledValue } from 'antd/lib/select';
|
||||||
import { debounce, isEqual, uniq } from 'lodash';
|
import { debounce, isEqual, uniq } from 'lodash';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { FAST_DEBOUNCE, SLOW_DEBOUNCE } from 'src/constants';
|
import { FAST_DEBOUNCE, SLOW_DEBOUNCE } from 'src/constants';
|
||||||
import {
|
import {
|
||||||
getValue,
|
getValue,
|
||||||
|
|
|
@ -25,7 +25,12 @@ import React, {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { SelectValue } from 'antd/lib/select';
|
import { SelectValue } from 'antd/lib/select';
|
||||||
|
|
||||||
import { styled, t } from '@superset-ui/core';
|
import {
|
||||||
|
styled,
|
||||||
|
t,
|
||||||
|
getClientErrorMessage,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { Select } from 'src/components';
|
import { Select } from 'src/components';
|
||||||
import { FormLabel } from 'src/components/Form';
|
import { FormLabel } from 'src/components/Form';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
|
@ -37,10 +42,6 @@ import CertifiedBadge from 'src/components/CertifiedBadge';
|
||||||
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
||||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||||
import { useTables, Table } from 'src/hooks/apiResources';
|
import { useTables, Table } from 'src/hooks/apiResources';
|
||||||
import {
|
|
||||||
getClientErrorMessage,
|
|
||||||
getClientErrorObject,
|
|
||||||
} from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
const REFRESH_WIDTH = 30;
|
const REFRESH_WIDTH = 30;
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,16 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupersetClient, t } from '@superset-ui/core';
|
import {
|
||||||
|
ClientErrorObject,
|
||||||
|
getClientErrorObject,
|
||||||
|
SupersetClient,
|
||||||
|
t,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import Tag from 'src/types/TagType';
|
import Tag from 'src/types/TagType';
|
||||||
|
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { cacheWrapper } from 'src/utils/cacheWrapper';
|
import { cacheWrapper } from 'src/utils/cacheWrapper';
|
||||||
import {
|
|
||||||
ClientErrorObject,
|
|
||||||
getClientErrorObject,
|
|
||||||
} from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
const localCache = new Map<string, any>();
|
const localCache = new Map<string, any>();
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { makeApi, CategoricalColorNamespace, t } from '@superset-ui/core';
|
import {
|
||||||
|
makeApi,
|
||||||
|
CategoricalColorNamespace,
|
||||||
|
t,
|
||||||
|
getErrorText,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import { getErrorText } from 'src/utils/getClientErrorObject';
|
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import {
|
import {
|
||||||
ChartConfiguration,
|
ChartConfiguration,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
getSharedLabelColor,
|
getSharedLabelColor,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
addChart,
|
addChart,
|
||||||
|
@ -34,7 +35,6 @@ import {
|
||||||
} from 'src/components/Chart/chartAction';
|
} from 'src/components/Chart/chartAction';
|
||||||
import { chart as initChart } from 'src/components/Chart/chartReducer';
|
import { chart as initChart } from 'src/components/Chart/chartReducer';
|
||||||
import { applyDefaultFormData } from 'src/explore/store';
|
import { applyDefaultFormData } from 'src/explore/store';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import {
|
import {
|
||||||
SAVE_TYPE_OVERWRITE,
|
SAVE_TYPE_OVERWRITE,
|
||||||
SAVE_TYPE_OVERWRITE_CONFIRMED,
|
SAVE_TYPE_OVERWRITE_CONFIRMED,
|
||||||
|
|
|
@ -17,9 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { DatasourceType, SupersetClient, t } from '@superset-ui/core';
|
import {
|
||||||
|
DatasourceType,
|
||||||
|
SupersetClient,
|
||||||
|
t,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { Slice } from '../types';
|
import { Slice } from '../types';
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
styled,
|
styled,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
t,
|
t,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
import Modal from 'src/components/Modal';
|
import Modal from 'src/components/Modal';
|
||||||
|
@ -41,7 +42,6 @@ import { JsonEditor } from 'src/components/AsyncAceEditor';
|
||||||
|
|
||||||
import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper';
|
import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper';
|
||||||
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
|
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import TagType from 'src/types/TagType';
|
import TagType from 'src/types/TagType';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { t } from '@superset-ui/core';
|
import { getClientErrorObject, t } from '@superset-ui/core';
|
||||||
import Popover, { PopoverProps } from 'src/components/Popover';
|
import Popover, { PopoverProps } from 'src/components/Popover';
|
||||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||||
import { getDashboardPermalink } from 'src/utils/urlUtils';
|
import { getDashboardPermalink } from 'src/utils/urlUtils';
|
||||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from 'src/dashboard/types';
|
import { RootState } from 'src/dashboard/types';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
export type URLShortLinkButtonProps = {
|
export type URLShortLinkButtonProps = {
|
||||||
dashboardId: number;
|
dashboardId: number;
|
||||||
|
|
|
@ -35,6 +35,8 @@ import {
|
||||||
styled,
|
styled,
|
||||||
SuperChart,
|
SuperChart,
|
||||||
t,
|
t,
|
||||||
|
ClientErrorObject,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { isEqual, isEqualWith } from 'lodash';
|
import { isEqual, isEqualWith } from 'lodash';
|
||||||
|
@ -43,10 +45,6 @@ import Loading from 'src/components/Loading';
|
||||||
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
|
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
|
||||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||||
import {
|
|
||||||
ClientErrorObject,
|
|
||||||
getClientErrorObject,
|
|
||||||
} from 'src/utils/getClientErrorObject';
|
|
||||||
import { FilterBarOrientation, RootState } from 'src/dashboard/types';
|
import { FilterBarOrientation, RootState } from 'src/dashboard/types';
|
||||||
import {
|
import {
|
||||||
onFiltersRefreshSuccess,
|
onFiltersRefreshSuccess,
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
import * as utils from 'src/utils/getClientErrorObject';
|
import * as uiCore from '@superset-ui/core';
|
||||||
import { Column, JsonObject } from '@superset-ui/core';
|
import { Column, JsonObject } from '@superset-ui/core';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { ColumnSelect } from './ColumnSelect';
|
import { ColumnSelect } from './ColumnSelect';
|
||||||
|
@ -97,7 +97,7 @@ test('Should call "getClientErrorObject" when api returns an error', async () =>
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
|
|
||||||
props.datasetId = 789;
|
props.datasetId = 789;
|
||||||
const spy = jest.spyOn(utils, 'getClientErrorObject');
|
const spy = jest.spyOn(uiCore, 'getClientErrorObject');
|
||||||
|
|
||||||
expect(spy).not.toBeCalled();
|
expect(spy).not.toBeCalled();
|
||||||
render(<ColumnSelect {...(props as any)} />, {
|
render(<ColumnSelect {...(props as any)} />, {
|
||||||
|
|
|
@ -18,10 +18,15 @@
|
||||||
*/
|
*/
|
||||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { Column, ensureIsArray, t, useChangeEffect } from '@superset-ui/core';
|
import {
|
||||||
|
Column,
|
||||||
|
ensureIsArray,
|
||||||
|
t,
|
||||||
|
useChangeEffect,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { Select, FormInstance } from 'src/components';
|
import { Select, FormInstance } from 'src/components';
|
||||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
||||||
import { NativeFiltersForm } from '../types';
|
import { NativeFiltersForm } from '../types';
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
*/
|
*/
|
||||||
import React, { useCallback, useMemo, ReactNode } from 'react';
|
import React, { useCallback, useMemo, ReactNode } from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { t, JsonResponse } from '@superset-ui/core';
|
|
||||||
import { AsyncSelect } from 'src/components';
|
|
||||||
import {
|
import {
|
||||||
|
t,
|
||||||
|
JsonResponse,
|
||||||
ClientErrorObject,
|
ClientErrorObject,
|
||||||
getClientErrorObject,
|
getClientErrorObject,
|
||||||
} from 'src/utils/getClientErrorObject';
|
} from '@superset-ui/core';
|
||||||
|
import { AsyncSelect } from 'src/components';
|
||||||
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
||||||
import {
|
import {
|
||||||
Dataset,
|
Dataset,
|
||||||
|
|
|
@ -37,6 +37,8 @@ import {
|
||||||
styled,
|
styled,
|
||||||
SupersetApiError,
|
SupersetApiError,
|
||||||
t,
|
t,
|
||||||
|
ClientErrorObject,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
|
@ -73,10 +75,6 @@ import {
|
||||||
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
|
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
|
||||||
import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
|
import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
|
||||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||||
import {
|
|
||||||
ClientErrorObject,
|
|
||||||
getClientErrorObject,
|
|
||||||
} from 'src/utils/getClientErrorObject';
|
|
||||||
import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
|
import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
|
||||||
import {
|
import {
|
||||||
getFormData,
|
getFormData,
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
*/
|
*/
|
||||||
import { DatasourceType } from '@superset-ui/core';
|
import { DatasourceType } from '@superset-ui/core';
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
import * as uiCore from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
setDatasource,
|
setDatasource,
|
||||||
changeDatasource,
|
changeDatasource,
|
||||||
saveDataset,
|
saveDataset,
|
||||||
} from 'src/explore/actions/datasourcesActions';
|
} from 'src/explore/actions/datasourcesActions';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import * as ClientError from 'src/utils/getClientErrorObject';
|
|
||||||
import datasourcesReducer from '../reducers/datasourcesReducer';
|
import datasourcesReducer from '../reducers/datasourcesReducer';
|
||||||
import { updateFormDataByDatasource } from './exploreActions';
|
import { updateFormDataByDatasource } from './exploreActions';
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ test('updateSlice with add to existing dashboard handles failure', async () => {
|
||||||
const sampleError = new Error('sampleError');
|
const sampleError = new Error('sampleError');
|
||||||
fetchMock.post(saveDatasetEndpoint, { throws: sampleError });
|
fetchMock.post(saveDatasetEndpoint, { throws: sampleError });
|
||||||
const dispatch = sinon.spy();
|
const dispatch = sinon.spy();
|
||||||
const errorSpy = jest.spyOn(ClientError, 'getClientErrorObject');
|
const errorSpy = jest.spyOn(uiCore, 'getClientErrorObject');
|
||||||
|
|
||||||
let caughtError;
|
let caughtError;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -20,9 +20,8 @@
|
||||||
import { Dispatch, AnyAction } from 'redux';
|
import { Dispatch, AnyAction } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { Dataset } from '@superset-ui/chart-controls';
|
import { Dataset } from '@superset-ui/chart-controls';
|
||||||
import { SupersetClient } from '@superset-ui/core';
|
import { SupersetClient, getClientErrorObject } from '@superset-ui/core';
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { updateFormDataByDatasource } from './exploreActions';
|
import { updateFormDataByDatasource } from './exploreActions';
|
||||||
import { ExplorePageState } from '../types';
|
import { ExplorePageState } from '../types';
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ import {
|
||||||
styled,
|
styled,
|
||||||
t,
|
t,
|
||||||
getChartMetadataRegistry,
|
getChartMetadataRegistry,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import { EmptyStateMedium } from 'src/components/EmptyState';
|
import { EmptyStateMedium } from 'src/components/EmptyState';
|
||||||
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { ResultsPaneProps, QueryResultInterface } from '../types';
|
import { ResultsPaneProps, QueryResultInterface } from '../types';
|
||||||
import { SingleQueryResultPane } from './SingleQueryResultPane';
|
import { SingleQueryResultPane } from './SingleQueryResultPane';
|
||||||
import { TableControls } from './DataTableControls';
|
import { TableControls } from './DataTableControls';
|
||||||
|
|
|
@ -29,9 +29,9 @@ import {
|
||||||
styled,
|
styled,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Chart, { Slice } from 'src/types/Chart';
|
import Chart, { Slice } from 'src/types/Chart';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import { loadTags } from 'src/components/Tags/utils';
|
import { loadTags } from 'src/components/Tags/utils';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
NO_TIME_RANGE,
|
NO_TIME_RANGE,
|
||||||
SupersetTheme,
|
SupersetTheme,
|
||||||
useCSSTextTruncation,
|
useCSSTextTruncation,
|
||||||
|
fetchTimeRange,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||||
|
@ -41,7 +42,6 @@ import ControlPopover from '../ControlPopover/ControlPopover';
|
||||||
import { DateFilterControlProps, FrameType } from './types';
|
import { DateFilterControlProps, FrameType } from './types';
|
||||||
import {
|
import {
|
||||||
DateFilterTestKey,
|
DateFilterTestKey,
|
||||||
fetchTimeRange,
|
|
||||||
FRAME_OPTIONS,
|
FRAME_OPTIONS,
|
||||||
guessFrame,
|
guessFrame,
|
||||||
useDefaultTimeFilter,
|
useDefaultTimeFilter,
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { t } from '@superset-ui/core';
|
import { SEPARATOR, t } from '@superset-ui/core';
|
||||||
import { SEPARATOR } from 'src/explore/components/controls/DateFilterControl/utils';
|
|
||||||
import { Input } from 'src/components/Input';
|
import { Input } from 'src/components/Input';
|
||||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||||
import { FrameComponentProps } from 'src/explore/components/controls/DateFilterControl/types';
|
import { FrameComponentProps } from 'src/explore/components/controls/DateFilterControl/types';
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
import {
|
import {
|
||||||
customTimeRangeEncode,
|
customTimeRangeEncode,
|
||||||
customTimeRangeDecode,
|
customTimeRangeDecode,
|
||||||
buildTimeRangeString,
|
|
||||||
formatTimeRange,
|
|
||||||
} from 'src/explore/components/controls/DateFilterControl/utils';
|
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||||
|
|
||||||
describe('Custom TimeRange', () => {
|
describe('Custom TimeRange', () => {
|
||||||
|
@ -298,34 +296,3 @@ describe('Custom TimeRange', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildTimeRangeString', () => {
|
|
||||||
it('generates proper time range string', () => {
|
|
||||||
expect(
|
|
||||||
buildTimeRangeString('2010-07-30T00:00:00', '2020-07-30T00:00:00'),
|
|
||||||
).toBe('2010-07-30T00:00:00 : 2020-07-30T00:00:00');
|
|
||||||
expect(buildTimeRangeString('', '2020-07-30T00:00:00')).toBe(
|
|
||||||
' : 2020-07-30T00:00:00',
|
|
||||||
);
|
|
||||||
expect(buildTimeRangeString('', '')).toBe(' : ');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('formatTimeRange', () => {
|
|
||||||
it('generates a readable time range', () => {
|
|
||||||
expect(formatTimeRange('Last 7 days')).toBe('Last 7 days');
|
|
||||||
expect(formatTimeRange('No filter')).toBe('No filter');
|
|
||||||
expect(formatTimeRange('Yesterday : Tomorrow')).toBe(
|
|
||||||
'Yesterday ≤ col < Tomorrow',
|
|
||||||
);
|
|
||||||
expect(formatTimeRange('2010-07-30T00:00:00 : 2020-07-30T00:00:00')).toBe(
|
|
||||||
'2010-07-30 ≤ col < 2020-07-30',
|
|
||||||
);
|
|
||||||
expect(formatTimeRange('2010-07-30T01:00:00 : ')).toBe(
|
|
||||||
'2010-07-30T01:00:00 ≤ col < ∞',
|
|
||||||
);
|
|
||||||
expect(formatTimeRange(' : 2020-07-30T00:00:00')).toBe(
|
|
||||||
'-∞ ≤ col < 2020-07-30',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -16,9 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import rison from 'rison';
|
import { NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
|
||||||
import { SupersetClient, NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
|
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
COMMON_RANGE_VALUES_SET,
|
COMMON_RANGE_VALUES_SET,
|
||||||
|
@ -27,26 +25,6 @@ import {
|
||||||
} from '.';
|
} from '.';
|
||||||
import { FrameType } from '../types';
|
import { FrameType } from '../types';
|
||||||
|
|
||||||
export const SEPARATOR = ' : ';
|
|
||||||
|
|
||||||
export const buildTimeRangeString = (since: string, until: string): string =>
|
|
||||||
`${since}${SEPARATOR}${until}`;
|
|
||||||
|
|
||||||
const formatDateEndpoint = (dttm: string, isStart?: boolean): string =>
|
|
||||||
dttm.replace('T00:00:00', '') || (isStart ? '-∞' : '∞');
|
|
||||||
|
|
||||||
export const formatTimeRange = (
|
|
||||||
timeRange: string,
|
|
||||||
columnPlaceholder = 'col',
|
|
||||||
) => {
|
|
||||||
const splitDateRange = timeRange.split(SEPARATOR);
|
|
||||||
if (splitDateRange.length === 1) return timeRange;
|
|
||||||
return `${formatDateEndpoint(
|
|
||||||
splitDateRange[0],
|
|
||||||
true,
|
|
||||||
)} ≤ ${columnPlaceholder} < ${formatDateEndpoint(splitDateRange[1])}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const guessFrame = (timeRange: string): FrameType => {
|
export const guessFrame = (timeRange: string): FrameType => {
|
||||||
if (COMMON_RANGE_VALUES_SET.has(timeRange)) {
|
if (COMMON_RANGE_VALUES_SET.has(timeRange)) {
|
||||||
return 'Common';
|
return 'Common';
|
||||||
|
@ -63,29 +41,6 @@ export const guessFrame = (timeRange: string): FrameType => {
|
||||||
return 'Advanced';
|
return 'Advanced';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTimeRange = async (
|
|
||||||
timeRange: string,
|
|
||||||
columnPlaceholder = 'col',
|
|
||||||
) => {
|
|
||||||
const query = rison.encode_uri(timeRange);
|
|
||||||
const endpoint = `/api/v1/time_range/?q=${query}`;
|
|
||||||
try {
|
|
||||||
const response = await SupersetClient.get({ endpoint });
|
|
||||||
const timeRangeString = buildTimeRangeString(
|
|
||||||
response?.json?.result[0]?.since || '',
|
|
||||||
response?.json?.result[0]?.until || '',
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
value: formatTimeRange(timeRangeString, columnPlaceholder),
|
|
||||||
};
|
|
||||||
} catch (response) {
|
|
||||||
const clientError = await getClientErrorObject(response);
|
|
||||||
return {
|
|
||||||
error: clientError.message || clientError.error || response.statusText,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useDefaultTimeFilter() {
|
export function useDefaultTimeFilter() {
|
||||||
return (
|
return (
|
||||||
useSelector(
|
useSelector(
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import moment, { Moment } from 'moment';
|
import moment, { Moment } from 'moment';
|
||||||
|
import { SEPARATOR } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
CustomRangeDecodeType,
|
CustomRangeDecodeType,
|
||||||
CustomRangeType,
|
CustomRangeType,
|
||||||
DateTimeGrainType,
|
DateTimeGrainType,
|
||||||
DateTimeModeType,
|
DateTimeModeType,
|
||||||
} from 'src/explore/components/controls/DateFilterControl/types';
|
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||||
import { SEPARATOR } from './dateFilterUtils';
|
|
||||||
import { SEVEN_DAYS_AGO, MIDNIGHT, MOMENT_FORMAT } from './constants';
|
import { SEVEN_DAYS_AGO, MIDNIGHT, MOMENT_FORMAT } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||||
|
import * as uiCore from '@superset-ui/core';
|
||||||
import { Operators } from 'src/explore/constants';
|
import { Operators } from 'src/explore/constants';
|
||||||
import * as FetchTimeRangeModule from 'src/explore/components/controls/DateFilterControl';
|
|
||||||
import { useGetTimeRangeLabel } from './useGetTimeRangeLabel';
|
import { useGetTimeRangeLabel } from './useGetTimeRangeLabel';
|
||||||
import AdhocFilter from '../AdhocFilter';
|
import AdhocFilter from '../AdhocFilter';
|
||||||
import { Clauses, ExpressionTypes } from '../types';
|
import { Clauses, ExpressionTypes } from '../types';
|
||||||
|
@ -65,7 +65,7 @@ test('should get "No filter" label', () => {
|
||||||
|
|
||||||
test('should get actualTimeRange and title', async () => {
|
test('should get actualTimeRange and title', async () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
.spyOn(uiCore, 'fetchTimeRange')
|
||||||
.mockResolvedValue({ value: 'MOCK TIME' });
|
.mockResolvedValue({ value: 'MOCK TIME' });
|
||||||
|
|
||||||
const adhocFilter = new AdhocFilter({
|
const adhocFilter = new AdhocFilter({
|
||||||
|
@ -85,7 +85,7 @@ test('should get actualTimeRange and title', async () => {
|
||||||
|
|
||||||
test('should get actualTimeRange and title when gets an error', async () => {
|
test('should get actualTimeRange and title when gets an error', async () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
.spyOn(uiCore, 'fetchTimeRange')
|
||||||
.mockResolvedValue({ error: 'MOCK ERROR' });
|
.mockResolvedValue({ error: 'MOCK ERROR' });
|
||||||
|
|
||||||
const adhocFilter = new AdhocFilter({
|
const adhocFilter = new AdhocFilter({
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
import { NO_TIME_RANGE, fetchTimeRange } from '@superset-ui/core';
|
||||||
import { fetchTimeRange } from 'src/explore/components/controls/DateFilterControl';
|
|
||||||
import { Operators } from 'src/explore/constants';
|
import { Operators } from 'src/explore/constants';
|
||||||
import AdhocFilter from '../AdhocFilter';
|
import AdhocFilter from '../AdhocFilter';
|
||||||
import { ExpressionTypes } from '../types';
|
import { ExpressionTypes } from '../types';
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { t, SupersetClient } from '@superset-ui/core';
|
import { t, SupersetClient, getClientErrorObject } from '@superset-ui/core';
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||||
import { Select } from 'src/components';
|
import { Select } from 'src/components';
|
||||||
import { SelectOptionsType, SelectProps } from 'src/components/Select/types';
|
import { SelectOptionsType, SelectProps } from 'src/components/Select/types';
|
||||||
import { SelectValue, LabeledValue } from 'antd/lib/select';
|
import { SelectValue, LabeledValue } from 'antd/lib/select';
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
type SelectAsyncProps = Omit<SelectProps, 'options' | 'ariaLabel' | 'onChange'>;
|
type SelectAsyncProps = Omit<SelectProps, 'options' | 'ariaLabel' | 'onChange'>;
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { styled, ensureIsArray, t } from '@superset-ui/core';
|
import {
|
||||||
|
styled,
|
||||||
|
ensureIsArray,
|
||||||
|
t,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
||||||
import ViewQuery from 'src/explore/components/controls/ViewQuery';
|
import ViewQuery from 'src/explore/components/controls/ViewQuery';
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,8 @@ import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { t, SupersetTheme } from '@superset-ui/core';
|
import { t, SupersetTheme, getClientErrorObject } from '@superset-ui/core';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import {
|
import {
|
||||||
addReport,
|
addReport,
|
||||||
editReport,
|
editReport,
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
|
import { createApi, BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
||||||
import {
|
import {
|
||||||
ClientErrorObject,
|
ClientErrorObject,
|
||||||
getClientErrorObject,
|
getClientErrorObject,
|
||||||
} from 'src/utils/getClientErrorObject';
|
|
||||||
import { createApi, BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
|
||||||
import {
|
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
ParseMethod,
|
ParseMethod,
|
||||||
SupersetClientResponse,
|
SupersetClientResponse,
|
||||||
|
|
|
@ -20,7 +20,6 @@ import fetchMock from 'fetch-mock';
|
||||||
import WS from 'jest-websocket-mock';
|
import WS from 'jest-websocket-mock';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import * as uiCore from '@superset-ui/core';
|
import * as uiCore from '@superset-ui/core';
|
||||||
import { parseErrorJson } from 'src/utils/getClientErrorObject';
|
|
||||||
import * as asyncEvent from 'src/middleware/asyncEvent';
|
import * as asyncEvent from 'src/middleware/asyncEvent';
|
||||||
|
|
||||||
describe('asyncEvent middleware', () => {
|
describe('asyncEvent middleware', () => {
|
||||||
|
@ -129,7 +128,7 @@ describe('asyncEvent middleware', () => {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { result: [asyncErrorEvent] },
|
body: { result: [asyncErrorEvent] },
|
||||||
});
|
});
|
||||||
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
const errorResponse = await uiCore.parseErrorJson(asyncErrorEvent);
|
||||||
await expect(
|
await expect(
|
||||||
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
||||||
).rejects.toEqual(errorResponse);
|
).rejects.toEqual(errorResponse);
|
||||||
|
@ -204,7 +203,7 @@ describe('asyncEvent middleware', () => {
|
||||||
|
|
||||||
wsServer.send(JSON.stringify(asyncErrorEvent));
|
wsServer.send(JSON.stringify(asyncErrorEvent));
|
||||||
|
|
||||||
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
const errorResponse = await uiCore.parseErrorJson(asyncErrorEvent);
|
||||||
|
|
||||||
await expect(promise).rejects.toEqual(errorResponse);
|
await expect(promise).rejects.toEqual(errorResponse);
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,11 @@ import {
|
||||||
makeApi,
|
makeApi,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
logging,
|
logging,
|
||||||
} from '@superset-ui/core';
|
|
||||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
|
||||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
|
||||||
import {
|
|
||||||
getClientErrorObject,
|
getClientErrorObject,
|
||||||
parseErrorJson,
|
parseErrorJson,
|
||||||
} from '../utils/getClientErrorObject';
|
SupersetError,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||||
|
|
||||||
type AsyncEvent = {
|
type AsyncEvent = {
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
|
|
|
@ -19,7 +19,13 @@
|
||||||
|
|
||||||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||||
import { useParams, Link, useHistory } from 'react-router-dom';
|
import { useParams, Link, useHistory } from 'react-router-dom';
|
||||||
import { css, t, styled, SupersetClient } from '@superset-ui/core';
|
import {
|
||||||
|
css,
|
||||||
|
t,
|
||||||
|
styled,
|
||||||
|
SupersetClient,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
|
|
||||||
|
@ -28,7 +34,6 @@ import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
import DeleteModal from 'src/components/DeleteModal';
|
import DeleteModal from 'src/components/DeleteModal';
|
||||||
import ListView, { ListViewProps } from 'src/components/ListView';
|
import ListView, { ListViewProps } from 'src/components/ListView';
|
||||||
import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
|
import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||||
import { createErrorHandler } from 'src/views/CRUD/utils';
|
import { createErrorHandler } from 'src/views/CRUD/utils';
|
||||||
|
|
|
@ -26,12 +26,12 @@ import {
|
||||||
makeApi,
|
makeApi,
|
||||||
SharedLabelColorSource,
|
SharedLabelColorSource,
|
||||||
t,
|
t,
|
||||||
|
getClientErrorObject,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import { getUrlParam } from 'src/utils/urlUtils';
|
import { getUrlParam } from 'src/utils/urlUtils';
|
||||||
import { URL_PARAMS } from 'src/constants';
|
import { URL_PARAMS } from 'src/constants';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import getFormDataWithExtraFilters from 'src/dashboard/util/charts/getFormDataWithExtraFilters';
|
import getFormDataWithExtraFilters from 'src/dashboard/util/charts/getFormDataWithExtraFilters';
|
||||||
import { getAppliedFilterValues } from 'src/dashboard/util/activeDashboardFilters';
|
import { getAppliedFilterValues } from 'src/dashboard/util/activeDashboardFilters';
|
||||||
import { getParsedExploreURLParams } from 'src/explore/exploreUtils/getParsedExploreURLParams';
|
import { getParsedExploreURLParams } from 'src/explore/exploreUtils/getParsedExploreURLParams';
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
*/
|
*/
|
||||||
/* eslint global-require: 0 */
|
/* eslint global-require: 0 */
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { SupersetClient } from '@superset-ui/core';
|
|
||||||
import {
|
import {
|
||||||
|
SupersetClient,
|
||||||
getClientErrorObject,
|
getClientErrorObject,
|
||||||
ClientErrorObject,
|
ClientErrorObject,
|
||||||
} from 'src/utils/getClientErrorObject';
|
} from '@superset-ui/core';
|
||||||
import setupErrorMessages from 'src/setup/setupErrorMessages';
|
import setupErrorMessages from 'src/setup/setupErrorMessages';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { ErrorTypeEnum } from '@superset-ui/core';
|
||||||
import getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
|
import getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
|
||||||
import { ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
|
||||||
import TimeoutErrorMessage from 'src/components/ErrorMessage/TimeoutErrorMessage';
|
import TimeoutErrorMessage from 'src/components/ErrorMessage/TimeoutErrorMessage';
|
||||||
import DatabaseErrorMessage from 'src/components/ErrorMessage/DatabaseErrorMessage';
|
import DatabaseErrorMessage from 'src/components/ErrorMessage/DatabaseErrorMessage';
|
||||||
import MarshmallowErrorMessage from 'src/components/ErrorMessage/MarshmallowErrorMessage';
|
import MarshmallowErrorMessage from 'src/components/ErrorMessage/MarshmallowErrorMessage';
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Error messages used in many places across applications
|
|
||||||
const COMMON_ERR_MESSAGES = {
|
|
||||||
SESSION_TIMED_OUT:
|
|
||||||
'Your session timed out, please refresh your page and try again.',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default COMMON_ERR_MESSAGES;
|
|
|
@ -1,83 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
import { ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
|
|
||||||
describe('getClientErrorObject()', () => {
|
|
||||||
it('Returns a Promise', () => {
|
|
||||||
const response = getClientErrorObject('error');
|
|
||||||
expect(response instanceof Promise).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Returns a Promise that resolves to an object with an error key', () => {
|
|
||||||
const error = 'error';
|
|
||||||
|
|
||||||
return getClientErrorObject(error).then(errorObj => {
|
|
||||||
expect(errorObj).toMatchObject({ error });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Handles Response that can be parsed as json', () => {
|
|
||||||
const jsonError = { something: 'something', error: 'Error message' };
|
|
||||||
const jsonErrorString = JSON.stringify(jsonError);
|
|
||||||
|
|
||||||
return getClientErrorObject(new Response(jsonErrorString)).then(
|
|
||||||
errorObj => {
|
|
||||||
expect(errorObj).toMatchObject(jsonError);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Handles backwards compatibility between old error messages and the new SIP-40 errors format', () => {
|
|
||||||
const jsonError = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
error_type: ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
|
|
||||||
extra: { engine: 'presto', link: 'https://www.google.com' },
|
|
||||||
level: 'error',
|
|
||||||
message: 'presto error: test error',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const jsonErrorString = JSON.stringify(jsonError);
|
|
||||||
|
|
||||||
return getClientErrorObject(new Response(jsonErrorString)).then(
|
|
||||||
errorObj => {
|
|
||||||
expect(errorObj.error).toEqual(jsonError.errors[0].message);
|
|
||||||
expect(errorObj.link).toEqual(jsonError.errors[0].extra.link);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Handles Response that can be parsed as text', () => {
|
|
||||||
const textError = 'Hello I am a text error';
|
|
||||||
|
|
||||||
return getClientErrorObject(new Response(textError)).then(errorObj => {
|
|
||||||
expect(errorObj).toMatchObject({ error: textError });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Handles plain text as input', () => {
|
|
||||||
const error = 'error';
|
|
||||||
|
|
||||||
return getClientErrorObject(error).then(errorObj => {
|
|
||||||
expect(errorObj).toMatchObject({ error });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -18,7 +18,13 @@
|
||||||
*/
|
*/
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { makeApi, SupersetClient, t, JsonObject } from '@superset-ui/core';
|
import {
|
||||||
|
makeApi,
|
||||||
|
SupersetClient,
|
||||||
|
t,
|
||||||
|
JsonObject,
|
||||||
|
getClientErrorObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createErrorHandler,
|
createErrorHandler,
|
||||||
|
@ -33,7 +39,6 @@ import { FetchDataConfig } from 'src/components/ListView';
|
||||||
import { FilterValue } from 'src/components/ListView/types';
|
import { FilterValue } from 'src/components/ListView/types';
|
||||||
import Chart, { Slice } from 'src/types/Chart';
|
import Chart, { Slice } from 'src/types/Chart';
|
||||||
import copyTextToClipboard from 'src/utils/copy';
|
import copyTextToClipboard from 'src/utils/copy';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import SupersetText from 'src/utils/textUtils';
|
import SupersetText from 'src/utils/textUtils';
|
||||||
import { DatabaseObject } from 'src/features/databases/types';
|
import { DatabaseObject } from 'src/features/databases/types';
|
||||||
import { FavoriteStatus, ImportResourceName } from './types';
|
import { FavoriteStatus, ImportResourceName } from './types';
|
||||||
|
|
|
@ -24,12 +24,12 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
SupersetClientResponse,
|
SupersetClientResponse,
|
||||||
SupersetTheme,
|
SupersetTheme,
|
||||||
|
getClientErrorObject,
|
||||||
t,
|
t,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Chart from 'src/types/Chart';
|
import Chart from 'src/types/Chart';
|
||||||
import { intersection } from 'lodash';
|
import { intersection } from 'lodash';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|
||||||
import { FetchDataConfig, FilterValue } from 'src/components/ListView';
|
import { FetchDataConfig, FilterValue } from 'src/components/ListView';
|
||||||
import SupersetText from 'src/utils/textUtils';
|
import SupersetText from 'src/utils/textUtils';
|
||||||
import { findPermission } from 'src/utils/findPermission';
|
import { findPermission } from 'src/utils/findPermission';
|
||||||
|
|
|
@ -26,7 +26,7 @@ class SupersetErrorType(StrEnum):
|
||||||
"""
|
"""
|
||||||
Types of errors that can exist within Superset.
|
Types of errors that can exist within Superset.
|
||||||
|
|
||||||
Keep in sync with superset-frontend/src/components/ErrorMessage/types.ts
|
Keep in sync with superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Frontend errors
|
# Frontend errors
|
||||||
|
@ -196,7 +196,7 @@ class ErrorLevel(StrEnum):
|
||||||
"""
|
"""
|
||||||
Levels of errors that can exist within Superset.
|
Levels of errors that can exist within Superset.
|
||||||
|
|
||||||
Keep in sync with superset-frontend/src/components/ErrorMessage/types.ts
|
Keep in sync with superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INFO = "info"
|
INFO = "info"
|
||||||
|
|
Loading…
Reference in New Issue