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,
|
||||
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 * from './types';
|
||||
export * from './constants';
|
||||
export { default as __hack_reexport_connection } from './types';
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { JsonObject, SupersetClientResponse, t } from '@superset-ui/core';
|
||||
import {
|
||||
COMMON_ERR_MESSAGES,
|
||||
JsonObject,
|
||||
SupersetClientResponse,
|
||||
t,
|
||||
SupersetError,
|
||||
ErrorTypeEnum,
|
||||
} from 'src/components/ErrorMessage/types';
|
||||
import COMMON_ERR_MESSAGES from './errorMessages';
|
||||
} from '@superset-ui/core';
|
||||
|
||||
// The response always contains an error attribute, can contain anything from the
|
||||
// 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
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(
|
||||
response:
|
||||
| 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(
|
||||
message: string,
|
||||
clientError?: ClientErrorObject,
|
|
@ -31,6 +31,7 @@ export { default as normalizeOrderBy } from './normalizeOrderBy';
|
|||
export { normalizeTimeColumn } from './normalizeTimeColumn';
|
||||
export { default as extractQueryFields } from './extractQueryFields';
|
||||
export * from './getXAxis';
|
||||
export * from './getClientErrorObject';
|
||||
|
||||
export * from './types/AnnotationLayer';
|
||||
export * from './types/QueryFormData';
|
||||
|
|
|
@ -166,6 +166,7 @@ export interface QueryContext {
|
|||
form_data?: QueryFormData;
|
||||
}
|
||||
|
||||
// Keep in sync with superset/errors.py
|
||||
export const ErrorTypeEnum = {
|
||||
// Frontend errors
|
||||
FRONTEND_CSRF_ERROR: 'FRONTEND_CSRF_ERROR',
|
||||
|
@ -187,9 +188,10 @@ export const ErrorTypeEnum = {
|
|||
CONNECTION_UNKNOWN_DATABASE_ERROR: 'CONNECTION_UNKNOWN_DATABASE_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',
|
||||
SYNTAX_ERROR: 'SYNTAX_ERROR',
|
||||
CONNECTION_DATABASE_TIMEOUT: 'CONNECTION_DATABASE_TIMEOUT',
|
||||
|
||||
// Viz errors
|
||||
VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR',
|
||||
|
@ -203,12 +205,17 @@ export const ErrorTypeEnum = {
|
|||
DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR',
|
||||
QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_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
|
||||
BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR',
|
||||
DATABASE_NOT_FOUND_ERROR: 'DATABASE_NOT_FOUND_ERROR',
|
||||
|
||||
// Sqllab error
|
||||
// Sql Lab errors
|
||||
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',
|
||||
|
@ -218,6 +225,8 @@ export const ErrorTypeEnum = {
|
|||
SQLLAB_TIMEOUT_ERROR: 'SQLLAB_TIMEOUT_ERROR',
|
||||
RESULTS_BACKEND_ERROR: 'RESULTS_BACKEND_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_COMMAND_ERROR: 'GENERIC_COMMAND_ERROR',
|
||||
|
@ -226,16 +235,20 @@ export const ErrorTypeEnum = {
|
|||
// API errors
|
||||
INVALID_PAYLOAD_FORMAT_ERROR: 'INVALID_PAYLOAD_FORMAT_ERROR',
|
||||
INVALID_PAYLOAD_SCHEMA_ERROR: 'INVALID_PAYLOAD_SCHEMA_ERROR',
|
||||
MARSHMALLOW_ERROR: 'MARSHMALLOW_ERROR',
|
||||
|
||||
// Report errors
|
||||
REPORT_NOTIFICATION_ERROR: 'REPORT_NOTIFICATION_ERROR',
|
||||
} as const;
|
||||
|
||||
type ValueOf<T> = T[keyof T];
|
||||
|
||||
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 ErrorSource = 'dashboard' | 'explore' | 'sqllab';
|
||||
export type ErrorSource = 'dashboard' | 'explore' | 'sqllab' | 'crud';
|
||||
|
||||
export type SupersetError<ExtraType = Record<string, any> | null> = {
|
||||
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 getComparisonFilters } from './getComparisonFilters';
|
||||
export { SEPARATOR, fetchTimeRange } from './fetchTimeRange';
|
||||
|
|
|
@ -49,14 +49,13 @@ const mockLoadQueryData = jest.fn<Promise<unknown>, unknown[]>(
|
|||
createArrayPromise,
|
||||
);
|
||||
|
||||
const actual = jest.requireActual('../../../src/chart/clients/ChartClient');
|
||||
// ChartClient is now a mock
|
||||
jest.mock('../../../src/chart/clients/ChartClient', () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
jest.spyOn(actual, 'default').mockImplementation(() => ({
|
||||
loadDatasource: mockLoadDatasource,
|
||||
loadFormData: mockLoadFormData,
|
||||
loadQueryData: mockLoadQueryData,
|
||||
})),
|
||||
);
|
||||
}));
|
||||
|
||||
const ChartClientMock = ChartClient as jest.Mock<ChartClient>;
|
||||
|
||||
|
|
|
@ -25,13 +25,11 @@ import {
|
|||
SharedLabelColor,
|
||||
SharedLabelColorSource,
|
||||
} from '@superset-ui/core';
|
||||
import { getAnalogousColors } from '../../src/color/utils';
|
||||
|
||||
jest.mock('../../src/color/utils', () => ({
|
||||
getAnalogousColors: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ['red', 'green', 'blue']),
|
||||
}));
|
||||
const actual = jest.requireActual('../../src/color/utils');
|
||||
const getAnalogousColorsSpy = jest
|
||||
.spyOn(actual, 'getAnalogousColors')
|
||||
.mockImplementation(() => ['red', 'green', 'blue']);
|
||||
|
||||
describe('SharedLabelColor', () => {
|
||||
beforeAll(() => {
|
||||
|
@ -161,7 +159,7 @@ describe('SharedLabelColor', () => {
|
|||
sharedLabelColor.updateColorMap('', 'testColors');
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
expect(Object.fromEntries(colorMap)).not.toEqual({});
|
||||
expect(getAnalogousColors).not.toBeCalled();
|
||||
expect(getAnalogousColorsSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should use analagous colors', () => {
|
||||
|
@ -176,7 +174,7 @@ describe('SharedLabelColor', () => {
|
|||
sharedLabelColor.updateColorMap('', 'testColors');
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
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,
|
||||
t,
|
||||
isFeatureEnabled,
|
||||
COMMON_ERR_MESSAGES,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import { invert, mapKeys } from 'lodash';
|
||||
|
||||
|
@ -33,8 +35,6 @@ import {
|
|||
addSuccessToast as addSuccessToastAction,
|
||||
addWarningToast as addWarningToastAction,
|
||||
} 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 getBootstrapData from 'src/utils/getBootstrapData';
|
||||
import { logEvent } from 'src/logger/actions';
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
*/
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
|
||||
import {
|
||||
createWrapper,
|
||||
defaultStore as store,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import { initialState } from 'src/SqlLab/fixtures';
|
||||
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
|
||||
import { useAnnotations } from './useAnnotations';
|
||||
|
||||
const fakeApiResult = {
|
||||
|
|
|
@ -18,16 +18,14 @@
|
|||
*/
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { COMMON_ERR_MESSAGES, ClientErrorObject, t } from '@superset-ui/core';
|
||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
|
||||
import { VALIDATION_DEBOUNCE_MS } from 'src/SqlLab/constants';
|
||||
import {
|
||||
FetchValidationQueryParams,
|
||||
useQueryValidationsQuery,
|
||||
} from 'src/hooks/apiResources';
|
||||
import { useDebounceValue } from 'src/hooks/useDebounceValue';
|
||||
import { ClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
export function useAnnotations(params: FetchValidationQueryParams) {
|
||||
const { sql, dbId, schema, templateParams } = params;
|
||||
|
|
|
@ -23,13 +23,13 @@ import {
|
|||
t,
|
||||
useTheme,
|
||||
isFeatureEnabled,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import Button from 'src/components/Button';
|
||||
import Icons from 'src/components/Icons';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { storeQuery } from 'src/utils/common';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
|
||||
interface ShareSqlLabQueryProps {
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SupersetError } from '@superset-ui/core';
|
||||
import { useChartOwnerNames } from 'src/hooks/apiResources';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
||||
|
||||
interface Props {
|
||||
chartId: string;
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
SupersetClient,
|
||||
t,
|
||||
isFeatureEnabled,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import { getControlsState } from 'src/explore/store';
|
||||
import {
|
||||
|
@ -38,7 +39,6 @@ import {
|
|||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { logEvent } from 'src/logger/actions';
|
||||
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 { updateDataMask } from 'src/dataMask/actions';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
|
|
|
@ -24,7 +24,12 @@ import React, {
|
|||
useCallback,
|
||||
} from 'react';
|
||||
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 { ServerPagination, SortByType } from 'src/components/TableView/types';
|
||||
import StyledModal from 'src/components/Modal';
|
||||
|
@ -33,7 +38,6 @@ import { useListViewResource } from 'src/views/CRUD/hooks';
|
|||
import Dataset from 'src/types/Dataset';
|
||||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { AntdInput } from 'src/components';
|
||||
import { Input } from 'src/components/Input';
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
SupersetClient,
|
||||
t,
|
||||
withTheme,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import { Select, AsyncSelect, Row, Col } from 'src/components';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
|
@ -46,7 +47,6 @@ import Label from 'src/components/Label';
|
|||
import Loading from 'src/components/Loading';
|
||||
import TableSelector from 'src/components/TableSelector';
|
||||
import EditableTitle from 'src/components/EditableTitle';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
||||
import TextControl from 'src/explore/components/controls/TextControl';
|
||||
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
|
||||
|
|
|
@ -26,16 +26,16 @@ import {
|
|||
Metric,
|
||||
styled,
|
||||
SupersetClient,
|
||||
getClientErrorObject,
|
||||
t,
|
||||
SupersetError,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
import Modal from 'src/components/Modal';
|
||||
import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
|
||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
|
||||
const DatasourceEditor = AsyncEsmComponent(() => import('./DatasourceEditor'));
|
||||
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
|
||||
import React from 'react';
|
||||
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 { ErrorLevel } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 { ErrorLevel } from './types';
|
||||
|
||||
const StyledContainer = styled.div<{ level: ErrorLevel }>`
|
||||
display: flex;
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import DatabaseErrorMessage from './DatabaseErrorMessage';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import DatasetNotFoundErrorMessage from './DatasetNotFoundErrorMessage';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -20,10 +20,9 @@
|
|||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
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 ErrorAlert from './ErrorAlert';
|
||||
import { ErrorLevel, ErrorSource } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -17,14 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 Modal from 'src/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
import { isCurrentUserBot } from 'src/utils/isBot';
|
||||
|
||||
import Icons from 'src/components/Icons';
|
||||
import { ErrorLevel, ErrorSource } from './types';
|
||||
import CopyToClipboard from '../CopyToClipboard';
|
||||
|
||||
const ErrorAlertDiv = styled.div<{ level: ErrorLevel }>`
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace';
|
||||
import BasicErrorAlert from './BasicErrorAlert';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { ErrorSource, t, SupersetError } from '@superset-ui/core';
|
||||
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
|
||||
import { SupersetError, ErrorSource } from './types';
|
||||
import ErrorAlert from './ErrorAlert';
|
||||
|
||||
const DEFAULT_TITLE = t('Unexpected error');
|
||||
|
|
|
@ -20,8 +20,12 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import { ErrorLevel, ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
||||
import {
|
||||
ErrorLevel,
|
||||
ErrorTypeEnum,
|
||||
ThemeProvider,
|
||||
supersetTheme,
|
||||
} from '@superset-ui/core';
|
||||
import MarshmallowErrorMessage from './MarshmallowErrorMessage';
|
||||
|
||||
describe('MarshmallowErrorMessage', () => {
|
||||
|
|
|
@ -23,13 +23,14 @@ import { Provider } from 'react-redux';
|
|||
import { createStore } from 'redux';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
import OAuth2RedirectMessage from 'src/components/ErrorMessage/OAuth2RedirectMessage';
|
||||
import {
|
||||
ErrorLevel,
|
||||
ErrorSource,
|
||||
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 { triggerQuery } from 'src/components/Chart/chartAction';
|
||||
import { onRefresh } from 'src/dashboard/actions/dashboardState';
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from '@superset-ui/core';
|
||||
import React from 'react';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import ParameterErrorMessage from './ParameterErrorMessage';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { ErrorSource, ErrorTypeEnum, ErrorLevel } from '@superset-ui/core';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import TimeoutErrorMessage from './TimeoutErrorMessage';
|
||||
import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types';
|
||||
|
||||
jest.mock(
|
||||
'src/components/Icons/Icon',
|
||||
|
|
|
@ -17,95 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// Keep in sync with superset/views/errors.py
|
||||
export const ErrorTypeEnum = {
|
||||
// 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;
|
||||
};
|
||||
import { ReactNode, ComponentType } from 'react';
|
||||
import { ErrorSource, SupersetError } from '@superset-ui/core';
|
||||
|
||||
export type ErrorMessageComponentProps<ExtraType = Record<string, any> | null> =
|
||||
{
|
||||
error: SupersetError<ExtraType>;
|
||||
source?: ErrorSource;
|
||||
subtitle?: React.ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
};
|
||||
|
||||
export type ErrorMessageComponent =
|
||||
React.ComponentType<ErrorMessageComponentProps>;
|
||||
export type ErrorMessageComponent = ComponentType<ErrorMessageComponentProps>;
|
||||
|
|
|
@ -29,11 +29,15 @@ import React, {
|
|||
useImperativeHandle,
|
||||
ClipboardEvent,
|
||||
} 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 { debounce, isEqual, uniq } from 'lodash';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { FAST_DEBOUNCE, SLOW_DEBOUNCE } from 'src/constants';
|
||||
import {
|
||||
getValue,
|
||||
|
|
|
@ -25,7 +25,12 @@ import React, {
|
|||
} from 'react';
|
||||
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 { FormLabel } from 'src/components/Form';
|
||||
import Icons from 'src/components/Icons';
|
||||
|
@ -37,10 +42,6 @@ import CertifiedBadge from 'src/components/CertifiedBadge';
|
|||
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { useTables, Table } from 'src/hooks/apiResources';
|
||||
import {
|
||||
getClientErrorMessage,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
|
||||
const REFRESH_WIDTH = 30;
|
||||
|
||||
|
|
|
@ -17,15 +17,16 @@
|
|||
* 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 rison from 'rison';
|
||||
import { cacheWrapper } from 'src/utils/cacheWrapper';
|
||||
import {
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
|
||||
const localCache = new Map<string, any>();
|
||||
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 { getErrorText } from 'src/utils/getClientErrorObject';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import {
|
||||
ChartConfiguration,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
getSharedLabelColor,
|
||||
SupersetClient,
|
||||
t,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
addChart,
|
||||
|
@ -34,7 +35,6 @@ import {
|
|||
} from 'src/components/Chart/chartAction';
|
||||
import { chart as initChart } from 'src/components/Chart/chartReducer';
|
||||
import { applyDefaultFormData } from 'src/explore/store';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import {
|
||||
SAVE_TYPE_OVERWRITE,
|
||||
SAVE_TYPE_OVERWRITE_CONFIRMED,
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { Dispatch } from 'redux';
|
||||
import { Slice } from '../types';
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
styled,
|
||||
SupersetClient,
|
||||
t,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
import Modal from 'src/components/Modal';
|
||||
|
@ -41,7 +42,6 @@ import { JsonEditor } from 'src/components/AsyncAceEditor';
|
|||
|
||||
import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper';
|
||||
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import TagType from 'src/types/TagType';
|
||||
import {
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { getDashboardPermalink } from 'src/utils/urlUtils';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
|
||||
export type URLShortLinkButtonProps = {
|
||||
dashboardId: number;
|
||||
|
|
|
@ -35,6 +35,8 @@ import {
|
|||
styled,
|
||||
SuperChart,
|
||||
t,
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isEqual, isEqualWith } from 'lodash';
|
||||
|
@ -43,10 +45,6 @@ import Loading from 'src/components/Loading';
|
|||
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
import {
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
import { FilterBarOrientation, RootState } from 'src/dashboard/types';
|
||||
import {
|
||||
onFiltersRefreshSuccess,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import React from 'react';
|
||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||
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 userEvent from '@testing-library/user-event';
|
||||
import { ColumnSelect } from './ColumnSelect';
|
||||
|
@ -97,7 +97,7 @@ test('Should call "getClientErrorObject" when api returns an error', async () =>
|
|||
const props = createProps();
|
||||
|
||||
props.datasetId = 789;
|
||||
const spy = jest.spyOn(utils, 'getClientErrorObject');
|
||||
const spy = jest.spyOn(uiCore, 'getClientErrorObject');
|
||||
|
||||
expect(spy).not.toBeCalled();
|
||||
render(<ColumnSelect {...(props as any)} />, {
|
||||
|
|
|
@ -18,10 +18,15 @@
|
|||
*/
|
||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
||||
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 { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
||||
import { NativeFiltersForm } from '../types';
|
||||
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
*/
|
||||
import React, { useCallback, useMemo, ReactNode } from 'react';
|
||||
import rison from 'rison';
|
||||
import { t, JsonResponse } from '@superset-ui/core';
|
||||
import { AsyncSelect } from 'src/components';
|
||||
import {
|
||||
t,
|
||||
JsonResponse,
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
} from '@superset-ui/core';
|
||||
import { AsyncSelect } from 'src/components';
|
||||
import { cachedSupersetGet } from 'src/utils/cachedSupersetGet';
|
||||
import {
|
||||
Dataset,
|
||||
|
|
|
@ -37,6 +37,8 @@ import {
|
|||
styled,
|
||||
SupersetApiError,
|
||||
t,
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import React, {
|
||||
|
@ -73,10 +75,6 @@ import {
|
|||
import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
|
||||
import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
import {
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
|
||||
import {
|
||||
getFormData,
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
*/
|
||||
import { DatasourceType } from '@superset-ui/core';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import * as uiCore from '@superset-ui/core';
|
||||
import {
|
||||
setDatasource,
|
||||
changeDatasource,
|
||||
saveDataset,
|
||||
} from 'src/explore/actions/datasourcesActions';
|
||||
import sinon from 'sinon';
|
||||
import * as ClientError from 'src/utils/getClientErrorObject';
|
||||
import datasourcesReducer from '../reducers/datasourcesReducer';
|
||||
import { updateFormDataByDatasource } from './exploreActions';
|
||||
|
||||
|
@ -126,7 +126,7 @@ test('updateSlice with add to existing dashboard handles failure', async () => {
|
|||
const sampleError = new Error('sampleError');
|
||||
fetchMock.post(saveDatasetEndpoint, { throws: sampleError });
|
||||
const dispatch = sinon.spy();
|
||||
const errorSpy = jest.spyOn(ClientError, 'getClientErrorObject');
|
||||
const errorSpy = jest.spyOn(uiCore, 'getClientErrorObject');
|
||||
|
||||
let caughtError;
|
||||
try {
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
import { Dispatch, AnyAction } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
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 { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { updateFormDataByDatasource } from './exploreActions';
|
||||
import { ExplorePageState } from '../types';
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ import {
|
|||
styled,
|
||||
t,
|
||||
getChartMetadataRegistry,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { EmptyStateMedium } from 'src/components/EmptyState';
|
||||
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { ResultsPaneProps, QueryResultInterface } from '../types';
|
||||
import { SingleQueryResultPane } from './SingleQueryResultPane';
|
||||
import { TableControls } from './DataTableControls';
|
||||
|
|
|
@ -29,9 +29,9 @@ import {
|
|||
styled,
|
||||
isFeatureEnabled,
|
||||
FeatureFlag,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import Chart, { Slice } from 'src/types/Chart';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { loadTags } from 'src/components/Tags/utils';
|
||||
import {
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
NO_TIME_RANGE,
|
||||
SupersetTheme,
|
||||
useCSSTextTruncation,
|
||||
fetchTimeRange,
|
||||
} from '@superset-ui/core';
|
||||
import Button from 'src/components/Button';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
|
@ -41,7 +42,6 @@ import ControlPopover from '../ControlPopover/ControlPopover';
|
|||
import { DateFilterControlProps, FrameType } from './types';
|
||||
import {
|
||||
DateFilterTestKey,
|
||||
fetchTimeRange,
|
||||
FRAME_OPTIONS,
|
||||
guessFrame,
|
||||
useDefaultTimeFilter,
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { SEPARATOR } from 'src/explore/components/controls/DateFilterControl/utils';
|
||||
import { SEPARATOR, t } from '@superset-ui/core';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||
import { FrameComponentProps } from 'src/explore/components/controls/DateFilterControl/types';
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
import {
|
||||
customTimeRangeEncode,
|
||||
customTimeRangeDecode,
|
||||
buildTimeRangeString,
|
||||
formatTimeRange,
|
||||
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||
|
||||
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
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { SupersetClient, NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
COMMON_RANGE_VALUES_SET,
|
||||
|
@ -27,26 +25,6 @@ import {
|
|||
} from '.';
|
||||
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 => {
|
||||
if (COMMON_RANGE_VALUES_SET.has(timeRange)) {
|
||||
return 'Common';
|
||||
|
@ -63,29 +41,6 @@ export const guessFrame = (timeRange: string): FrameType => {
|
|||
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() {
|
||||
return (
|
||||
useSelector(
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
import moment, { Moment } from 'moment';
|
||||
import { SEPARATOR } from '@superset-ui/core';
|
||||
import {
|
||||
CustomRangeDecodeType,
|
||||
CustomRangeType,
|
||||
DateTimeGrainType,
|
||||
DateTimeModeType,
|
||||
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||
import { SEPARATOR } from './dateFilterUtils';
|
||||
import { SEVEN_DAYS_AGO, MIDNIGHT, MOMENT_FORMAT } from './constants';
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import * as uiCore from '@superset-ui/core';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
import * as FetchTimeRangeModule from 'src/explore/components/controls/DateFilterControl';
|
||||
import { useGetTimeRangeLabel } from './useGetTimeRangeLabel';
|
||||
import AdhocFilter from '../AdhocFilter';
|
||||
import { Clauses, ExpressionTypes } from '../types';
|
||||
|
@ -65,7 +65,7 @@ test('should get "No filter" label', () => {
|
|||
|
||||
test('should get actualTimeRange and title', async () => {
|
||||
jest
|
||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
||||
.spyOn(uiCore, 'fetchTimeRange')
|
||||
.mockResolvedValue({ value: 'MOCK TIME' });
|
||||
|
||||
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 () => {
|
||||
jest
|
||||
.spyOn(FetchTimeRangeModule, 'fetchTimeRange')
|
||||
.spyOn(uiCore, 'fetchTimeRange')
|
||||
.mockResolvedValue({ error: 'MOCK ERROR' });
|
||||
|
||||
const adhocFilter = new AdhocFilter({
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import { fetchTimeRange } from 'src/explore/components/controls/DateFilterControl';
|
||||
import { NO_TIME_RANGE, fetchTimeRange } from '@superset-ui/core';
|
||||
import { Operators } from 'src/explore/constants';
|
||||
import AdhocFilter from '../AdhocFilter';
|
||||
import { ExpressionTypes } from '../types';
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 { Select } from 'src/components';
|
||||
import { SelectOptionsType, SelectProps } from 'src/components/Select/types';
|
||||
import { SelectValue, LabeledValue } from 'antd/lib/select';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
|
||||
type SelectAsyncProps = Omit<SelectProps, 'options' | 'ariaLabel' | 'onChange'>;
|
||||
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
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 { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { getChartDataRequest } from 'src/components/Chart/chartAction';
|
||||
import ViewQuery from 'src/explore/components/controls/ViewQuery';
|
||||
|
||||
|
|
|
@ -23,9 +23,8 @@ import React, {
|
|||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { t, SupersetTheme } from '@superset-ui/core';
|
||||
import { t, SupersetTheme, getClientErrorObject } from '@superset-ui/core';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import {
|
||||
addReport,
|
||||
editReport,
|
||||
|
|
|
@ -17,12 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { createApi, BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
||||
import {
|
||||
ClientErrorObject,
|
||||
getClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
import { createApi, BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
||||
import {
|
||||
SupersetClient,
|
||||
ParseMethod,
|
||||
SupersetClientResponse,
|
||||
|
|
|
@ -20,7 +20,6 @@ import fetchMock from 'fetch-mock';
|
|||
import WS from 'jest-websocket-mock';
|
||||
import sinon from 'sinon';
|
||||
import * as uiCore from '@superset-ui/core';
|
||||
import { parseErrorJson } from 'src/utils/getClientErrorObject';
|
||||
import * as asyncEvent from 'src/middleware/asyncEvent';
|
||||
|
||||
describe('asyncEvent middleware', () => {
|
||||
|
@ -129,7 +128,7 @@ describe('asyncEvent middleware', () => {
|
|||
status: 200,
|
||||
body: { result: [asyncErrorEvent] },
|
||||
});
|
||||
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
||||
const errorResponse = await uiCore.parseErrorJson(asyncErrorEvent);
|
||||
await expect(
|
||||
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
||||
).rejects.toEqual(errorResponse);
|
||||
|
@ -204,7 +203,7 @@ describe('asyncEvent middleware', () => {
|
|||
|
||||
wsServer.send(JSON.stringify(asyncErrorEvent));
|
||||
|
||||
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
||||
const errorResponse = await uiCore.parseErrorJson(asyncErrorEvent);
|
||||
|
||||
await expect(promise).rejects.toEqual(errorResponse);
|
||||
|
||||
|
|
|
@ -23,13 +23,11 @@ import {
|
|||
makeApi,
|
||||
SupersetClient,
|
||||
logging,
|
||||
} from '@superset-ui/core';
|
||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
import {
|
||||
getClientErrorObject,
|
||||
parseErrorJson,
|
||||
} from '../utils/getClientErrorObject';
|
||||
SupersetError,
|
||||
} from '@superset-ui/core';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
|
||||
type AsyncEvent = {
|
||||
id?: string | null;
|
||||
|
|
|
@ -19,7 +19,13 @@
|
|||
|
||||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
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 rison from 'rison';
|
||||
|
||||
|
@ -28,7 +34,6 @@ import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
|||
import DeleteModal from 'src/components/DeleteModal';
|
||||
import ListView, { ListViewProps } from 'src/components/ListView';
|
||||
import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||
import { createErrorHandler } from 'src/views/CRUD/utils';
|
||||
|
|
|
@ -26,12 +26,12 @@ import {
|
|||
makeApi,
|
||||
SharedLabelColorSource,
|
||||
t,
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import getFormDataWithExtraFilters from 'src/dashboard/util/charts/getFormDataWithExtraFilters';
|
||||
import { getAppliedFilterValues } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { getParsedExploreURLParams } from 'src/explore/exploreUtils/getParsedExploreURLParams';
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
*/
|
||||
/* eslint global-require: 0 */
|
||||
import $ from 'jquery';
|
||||
import { SupersetClient } from '@superset-ui/core';
|
||||
import {
|
||||
SupersetClient,
|
||||
getClientErrorObject,
|
||||
ClientErrorObject,
|
||||
} from 'src/utils/getClientErrorObject';
|
||||
} from '@superset-ui/core';
|
||||
import setupErrorMessages from 'src/setup/setupErrorMessages';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ErrorTypeEnum } from '@superset-ui/core';
|
||||
import getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
|
||||
import { ErrorTypeEnum } from 'src/components/ErrorMessage/types';
|
||||
import TimeoutErrorMessage from 'src/components/ErrorMessage/TimeoutErrorMessage';
|
||||
import DatabaseErrorMessage from 'src/components/ErrorMessage/DatabaseErrorMessage';
|
||||
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 { 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 {
|
||||
createErrorHandler,
|
||||
|
@ -33,7 +39,6 @@ import { FetchDataConfig } from 'src/components/ListView';
|
|||
import { FilterValue } from 'src/components/ListView/types';
|
||||
import Chart, { Slice } from 'src/types/Chart';
|
||||
import copyTextToClipboard from 'src/utils/copy';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import SupersetText from 'src/utils/textUtils';
|
||||
import { DatabaseObject } from 'src/features/databases/types';
|
||||
import { FavoriteStatus, ImportResourceName } from './types';
|
||||
|
|
|
@ -24,12 +24,12 @@ import {
|
|||
SupersetClient,
|
||||
SupersetClientResponse,
|
||||
SupersetTheme,
|
||||
getClientErrorObject,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import Chart from 'src/types/Chart';
|
||||
import { intersection } from 'lodash';
|
||||
import rison from 'rison';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { FetchDataConfig, FilterValue } from 'src/components/ListView';
|
||||
import SupersetText from 'src/utils/textUtils';
|
||||
import { findPermission } from 'src/utils/findPermission';
|
||||
|
|
|
@ -26,7 +26,7 @@ class SupersetErrorType(StrEnum):
|
|||
"""
|
||||
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
|
||||
|
@ -196,7 +196,7 @@ class ErrorLevel(StrEnum):
|
|||
"""
|
||||
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"
|
||||
|
|
Loading…
Reference in New Issue