test",
diff --git a/__tests__/ui/__snapshots__/CopyButton.test.js.snap b/__tests__/ui/__snapshots__/CopyButton.test.js.snap
index f6fba8cac..e03c2fc1e 100644
--- a/__tests__/ui/__snapshots__/CopyButton.test.js.snap
+++ b/__tests__/ui/__snapshots__/CopyButton.test.js.snap
@@ -16,7 +16,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
`;
diff --git a/__tests__/ui/__snapshots__/DisabledSection.test.js.snap b/__tests__/ui/__snapshots__/DisabledSection.test.js.snap
index 8750f3641..957c707f2 100644
--- a/__tests__/ui/__snapshots__/DisabledSection.test.js.snap
+++ b/__tests__/ui/__snapshots__/DisabledSection.test.js.snap
@@ -2,7 +2,7 @@
exports[`matches enzyme snapshots matches snapshot - false 1`] = `
@@ -11,7 +11,7 @@ exports[`matches enzyme snapshots matches snapshot - false 1`] = `
exports[`matches enzyme snapshots matches snapshot 1`] = `
@@ -19,7 +19,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
diff --git a/__tests__/ui/__snapshots__/Dropdown.test.js.snap b/__tests__/ui/__snapshots__/Dropdown.test.js.snap
index 1756b4f34..e22b78dc2 100644
--- a/__tests__/ui/__snapshots__/Dropdown.test.js.snap
+++ b/__tests__/ui/__snapshots__/Dropdown.test.js.snap
@@ -27,7 +27,7 @@ exports[`matches test renderer snapshot serializes the styles 1`] = `
onMouseLeave={[Function]}
>
Test
@@ -10,7 +10,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
Test
diff --git a/__tests__/ui/__snapshots__/KeyboardKey.test.js.snap b/__tests__/ui/__snapshots__/KeyboardKey.test.js.snap
index 7306186bf..fdd67962c 100644
--- a/__tests__/ui/__snapshots__/KeyboardKey.test.js.snap
+++ b/__tests__/ui/__snapshots__/KeyboardKey.test.js.snap
@@ -8,7 +8,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
test
diff --git a/__tests__/ui/__snapshots__/Level.test.js.snap b/__tests__/ui/__snapshots__/Level.test.js.snap
index 89bb942da..45238bedd 100644
--- a/__tests__/ui/__snapshots__/Level.test.js.snap
+++ b/__tests__/ui/__snapshots__/Level.test.js.snap
@@ -2,7 +2,7 @@
exports[`matches enzyme snapshots matches snapshot 1`] = `
@@ -10,7 +10,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
@@ -14,17 +14,17 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
@@ -107,18 +107,18 @@ exports[`matches test renderer snapshot serializes the styles 1`] = `
className="Pagination-next"
>
Next
@@ -127,69 +127,69 @@ exports[`matches test renderer snapshot serializes the styles 1`] = `
>
1
2
3
4
@@ -202,18 +202,18 @@ exports[`matches test renderer snapshot serializes the styles 1`] = `
10
diff --git a/__tests__/ui/__snapshots__/QuerybookLogo.test.js.snap b/__tests__/ui/__snapshots__/QuerybookLogo.test.js.snap
index 3cc9104f3..aa70d8822 100644
--- a/__tests__/ui/__snapshots__/QuerybookLogo.test.js.snap
+++ b/__tests__/ui/__snapshots__/QuerybookLogo.test.js.snap
@@ -26,7 +26,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
diff --git a/__tests__/ui/__snapshots__/ToolBarButton.test.js.snap b/__tests__/ui/__snapshots__/ToolBarButton.test.js.snap
index 8f8eda7bc..0ee7c6961 100644
--- a/__tests__/ui/__snapshots__/ToolBarButton.test.js.snap
+++ b/__tests__/ui/__snapshots__/ToolBarButton.test.js.snap
@@ -16,7 +16,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
exports[`matches test renderer snapshot serializes the styles 1`] = `
diff --git a/components/Board/BoardError.tsx b/components/Board/BoardError.tsx
index bf268b414..6583aa8af 100644
--- a/components/Board/BoardError.tsx
+++ b/components/Board/BoardError.tsx
@@ -1,14 +1,14 @@
-import type { AxiosError } from 'axios';
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { AccessRequestButton } from 'components/AccessRequestButton/AccessRequestButton';
+import { AxiosErrorWithMessage } from 'lib/utils/error';
import { addBoardAccessRequest } from 'redux/board/action';
import { Dispatch } from 'redux/store/types';
import { ErrorPage } from 'ui/ErrorPage/ErrorPage';
export const BoardError: React.FunctionComponent<{
- errorObj: AxiosError;
+ errorObj: AxiosErrorWithMessage;
boardId: number;
}> = React.memo(({ boardId, errorObj }) => {
const dispatch: Dispatch = useDispatch();
diff --git a/components/DataDoc/DataDoc.tsx b/components/DataDoc/DataDoc.tsx
index a230dbbe9..34528e75e 100644
--- a/components/DataDoc/DataDoc.tsx
+++ b/components/DataDoc/DataDoc.tsx
@@ -42,7 +42,7 @@ import { replaceDataDoc, searchDataDocCells } from 'lib/data-doc/search';
import { sendConfirm, setBrowserTitle } from 'lib/querybookUI';
import history from 'lib/router-history';
import { copy, sanitizeUrlTitle } from 'lib/utils';
-import { formatError, isAxiosError } from 'lib/utils/error';
+import { formatError, isAxiosErrorWithMessage } from 'lib/utils/error';
import { KeyMap, matchKeyMap } from 'lib/utils/keyboard';
import { getQueryString } from 'lib/utils/query-string';
import * as dataDocActions from 'redux/dataDoc/action';
@@ -882,7 +882,7 @@ class DataDocComponent extends React.PureComponent {
const { dataDoc } = this.props;
const { errorObj } = this.state;
- if (isAxiosError(errorObj)) {
+ if (isAxiosErrorWithMessage(errorObj)) {
return (
);
diff --git a/components/DataDoc/DataDocError.tsx b/components/DataDoc/DataDocError.tsx
index b1060c99d..42a2ddf2c 100644
--- a/components/DataDoc/DataDocError.tsx
+++ b/components/DataDoc/DataDocError.tsx
@@ -1,14 +1,14 @@
-import { AxiosError } from 'axios';
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { AccessRequestButton } from 'components/AccessRequestButton/AccessRequestButton';
+import { AxiosErrorWithMessage } from 'lib/utils/error';
import * as dataDocActions from 'redux/dataDoc/action';
import { Dispatch } from 'redux/store/types';
import { ErrorPage } from 'ui/ErrorPage/ErrorPage';
export const DataDocError: React.FunctionComponent<{
- errorObj: AxiosError;
+ errorObj: AxiosErrorWithMessage;
docId: number;
}> = React.memo(({ docId, errorObj }) => {
let errorTitle: string;
diff --git a/components/DataTableViewSamples/DataTableViewSamples.tsx b/components/DataTableViewSamples/DataTableViewSamples.tsx
index a990363c9..092c214d4 100644
--- a/components/DataTableViewSamples/DataTableViewSamples.tsx
+++ b/components/DataTableViewSamples/DataTableViewSamples.tsx
@@ -11,7 +11,7 @@ import {
} from 'const/metastore';
import { useToggleState } from 'hooks/useToggleState';
import { format } from 'lib/sql-helper/sql-formatter';
-import { isAxiosError } from 'lib/utils/error';
+import { isAxiosErrorWithMessage } from 'lib/utils/error';
import * as dataSourcesActions from 'redux/dataSources/action';
import { Dispatch, IStoreState } from 'redux/store/types';
import { TableSamplesResource } from 'resource/table';
@@ -93,7 +93,7 @@ export const DataTableViewSamples: React.FunctionComponent<
);
setRawSamplesQuery(format(query, language));
} catch (error) {
- if (isAxiosError(error)) {
+ if (isAxiosErrorWithMessage(error)) {
const possibleErrorMessage = error?.response?.data?.error;
if (possibleErrorMessage) {
toast.error(
diff --git a/components/QueryEditor/QueryEditor.tsx b/components/QueryEditor/QueryEditor.tsx
index f1417f6f8..30bf994c0 100644
--- a/components/QueryEditor/QueryEditor.tsx
+++ b/components/QueryEditor/QueryEditor.tsx
@@ -26,7 +26,7 @@ import {
AutoCompleteType,
ExcludedTriggerKeys,
} from 'lib/sql-helper/sql-autocompleter';
-import { format } from 'lib/sql-helper/sql-formatter';
+import { format, ISQLFormatOptions } from 'lib/sql-helper/sql-formatter';
import {
ILinterWarning,
IRange,
@@ -232,20 +232,18 @@ export const QueryEditor: React.FC<
);
const formatQuery = useCallback(
- (
- options: {
- case?: 'lower' | 'upper';
- indent?: string;
- } = {}
- ) => {
+ (options: ISQLFormatOptions = {}) => {
if (editorRef.current) {
const indentWithTabs =
editorRef.current.getOption('indentWithTabs');
const indentUnit =
editorRef.current.getOption('indentUnit');
- options['indent'] = indentWithTabs
- ? '\t'
- : ' '.repeat(indentUnit);
+
+ if (indentWithTabs) {
+ options.useTabs = true;
+ } else {
+ options.tabWidth = indentUnit;
+ }
}
const formattedQuery = format(
diff --git a/lib/datasource.ts b/lib/datasource.ts
index 45e16d3c1..68a06b996 100644
--- a/lib/datasource.ts
+++ b/lib/datasource.ts
@@ -45,7 +45,7 @@ function syncDatasource(
if (data) {
if (method === 'GET') {
defaultConfig.params = {
- params: data,
+ params: JSON.stringify(data),
};
} else {
defaultConfig.data = data;
diff --git a/lib/sql-helper/sql-formatter.ts b/lib/sql-helper/sql-formatter.ts
index 99090b78f..c98360cc0 100644
--- a/lib/sql-helper/sql-formatter.ts
+++ b/lib/sql-helper/sql-formatter.ts
@@ -1,6 +1,6 @@
import { getQueryLinePosition, IToken, tokenize } from './sql-lexer';
import { find, invert, uniqueId } from 'lodash';
-import sqlFormatter from 'sql-formatter';
+import { format as sqlFormat, supportedDialects } from 'sql-formatter';
const skipTokenType = new Set(['TEMPLATED_TAG', 'TEMPLATED_BLOCK', 'URL']);
@@ -48,19 +48,23 @@ function tokensToText(tokens: IToken[]) {
};
}
+export interface ISQLFormatOptions {
+ case?: 'lower' | 'upper';
+ tabWidth?: number;
+ useTabs?: boolean;
+}
+
export function format(
query: string,
language: string,
- options?: {
- case?: 'lower' | 'upper';
- indent?: string;
- }
+ options?: ISQLFormatOptions
) {
options = {
...{
// default options
case: 'upper',
- indent: ' ',
+ tabWidth: 2,
+ useTabs: false,
},
...options,
};
@@ -133,9 +137,10 @@ export function format(
firstKeyWord &&
allowedStatement.has(firstKeyWord.text.toLocaleLowerCase())
) {
- formattedStatement = sqlFormatter.format(statementText, {
- indent: options.indent,
+ formattedStatement = sqlFormat(statementText, {
+ tabWidth: options.tabWidth,
language: getLanguageForSqlFormatter(language),
+ useTabs: options.useTabs,
});
}
@@ -157,13 +162,19 @@ export function format(
);
}
-const SQL_FORMATTER_LANGUAGES = ['db2', 'n1ql', 'pl/sql', 'sql'] as const;
+// Override according to https://github.com/sql-formatter-org/sql-formatter/blob/master/docs/language.md
+const languageMappingOverride = {
+ presto: 'trino',
+ sparksql: 'spark',
+};
-type SqlFormatterLanguage = typeof SQL_FORMATTER_LANGUAGES[number];
+function getLanguageForSqlFormatter(language: string): string {
+ if (supportedDialects.includes(language)) {
+ return language;
+ }
-function getLanguageForSqlFormatter(language: string): SqlFormatterLanguage {
- if ((SQL_FORMATTER_LANGUAGES as readonly string[]).includes(language)) {
- return language as SqlFormatterLanguage;
+ if (language in languageMappingOverride) {
+ return languageMappingOverride[language];
}
return 'sql';
diff --git a/lib/utils/error.ts b/lib/utils/error.ts
index 6ab808a7c..4669c1bc1 100644
--- a/lib/utils/error.ts
+++ b/lib/utils/error.ts
@@ -3,6 +3,7 @@ import moment from 'moment';
import { formatDuration, generateFormattedDate } from './datetime';
+export type AxiosErrorWithMessage = AxiosError<{ error?: string }>;
export function formatError(error: any): string {
if (typeof error === 'string') {
return error;
@@ -14,17 +15,11 @@ export function formatError(error: any): string {
error.constructor === Error;
if (isErrorObject) {
if (isAxiosError(error)) {
- if (error.response) {
- if (
- error.response.data &&
- typeof error.response.data === 'object'
- ) {
- // The request was made and the server responded with a status code > 2xx
- if ('error' in error.response.data) {
- return error.response.data.error;
- }
- }
+ if (isAxiosErrorWithMessage(error)) {
+ return error.response.data.error;
+ }
+ if (error.response) {
if (
error.response.status === 429 &&
'flask-limit-key' in error.response.headers
@@ -45,6 +40,19 @@ export function isAxiosError(e: any): e is AxiosError {
return e instanceof Error && (e as AxiosError).isAxiosError;
}
+export function isAxiosErrorWithMessage(e: any): e is AxiosErrorWithMessage {
+ if (
+ isAxiosError(e) &&
+ e.response &&
+ e.response.data &&
+ typeof e.response.data === 'object' &&
+ 'error' in e.response.data
+ ) {
+ return true;
+ }
+ return false;
+}
+
function formatRateLimitError(headers: Record) {
const limitKey = headers['flask-limit-key'];
const limitAmount = headers['flask-limit-amount'];
diff --git a/lib/utils/index.ts b/lib/utils/index.ts
index d5fba7dc7..c9d3e47e7 100644
--- a/lib/utils/index.ts
+++ b/lib/utils/index.ts
@@ -161,11 +161,12 @@ export function getQueryEngineId(
}
export function arrayGroupByField<
- T,
+ T extends Record,
K extends keyof PickType
>(array: T[], byField?: K): Record {
+ type GroupedRecord = Record;
if (array.length === 0) {
- return {};
+ return {} as GroupedRecord;
}
return array.reduce((result, item) => {
const identifier: string = item[
@@ -173,7 +174,7 @@ export function arrayGroupByField<
] as any as string;
result[identifier] = item;
return result;
- }, {});
+ }, {} as GroupedRecord);
}
// from https://stackoverflow.com/a/39494245
diff --git a/resource/table.ts b/resource/table.ts
index 62f869b1a..6e7e6bbce 100644
--- a/resource/table.ts
+++ b/resource/table.ts
@@ -36,7 +36,7 @@ export const TableSamplesResource = {
ds.fetch(
{
url: `/table/${tableId}/samples/`,
- transformResponse: [JSONBig.parse],
+ transformResponse: [(data) => JSONBig.parse(data)],
},
{
environment_id: environmentId,
@@ -52,7 +52,7 @@ export const TableSamplesResource = {
ds.save(
{
url: `/table/${tableId}/samples/`,
- transformResponse: [JSONBig.parse],
+ transformResponse: [(data) => JSONBig.parse(data)],
},
{
environment_id: environmentId,