Skip to content

Commit

Permalink
refactor: Move fetchTimeRange to core package (apache#27852)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgabryje authored and qleroy committed Apr 28, 2024
1 parent 4c55f85 commit f1fb073
Show file tree
Hide file tree
Showing 72 changed files with 623 additions and 430 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from './types';

export { default as getComparisonInfo } from './getComparisonInfo';
export { default as getComparisonFilters } from './getComparisonFilters';
export { SEPARATOR, fetchTimeRange } from './fetchTimeRange';
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => ({
loadDatasource: mockLoadDatasource,
loadFormData: mockLoadFormData,
loadQueryData: mockLoadQueryData,
})),
);
jest.spyOn(actual, 'default').mockImplementation(() => ({
loadDatasource: mockLoadDatasource,
loadFormData: mockLoadFormData,
loadQueryData: mockLoadQueryData,
}));

const ChartClientMock = ChartClient as jest.Mock<ChartClient>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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();
});
});

Expand Down
Loading

0 comments on commit f1fb073

Please sign in to comment.