diff --git a/__tests__/lib/sql-helper/sql-formatter.test.ts b/__tests__/lib/sql-helper/sql-formatter.test.ts index 41e74df8c..22712c5f5 100644 --- a/__tests__/lib/sql-helper/sql-formatter.test.ts +++ b/__tests__/lib/sql-helper/sql-formatter.test.ts @@ -12,7 +12,7 @@ FROM ); expect(format('select ARRAY [1] || ARRAY [2];', 'presto')).toBe( `SELECT - ARRAY [1] || ARRAY [2];` + ARRAY[1] || ARRAY[2];` ); }); test('Simple formatting with templating case', () => { @@ -58,7 +58,7 @@ from }); test('Simple formatting tab indent', () => { - expect(format('select * from test;', 'presto', { indent: '\t' })).toBe( + expect(format('select * from test;', 'presto', { useTabs: true })).toBe( `SELECT \t* FROM @@ -67,7 +67,7 @@ FROM }); test('Simple formatting four space indent', () => { - expect(format('select * from test;', 'presto', { indent: ' ' })).toBe( + expect(format('select * from test;', 'presto', { tabWidth: 4 })).toBe( `SELECT * FROM diff --git a/__tests__/ui/__snapshots__/AsyncButton.test.js.snap b/__tests__/ui/__snapshots__/AsyncButton.test.js.snap index 7451d061a..c07d8ce9a 100644 --- a/__tests__/ui/__snapshots__/AsyncButton.test.js.snap +++ b/__tests__/ui/__snapshots__/AsyncButton.test.js.snap @@ -28,19 +28,19 @@ exports[`matches enzyme snapshots matches snapshot 1`] = ` exports[`matches test renderer snapshot serializes the styles 1`] = `
test
`; diff --git a/__tests__/ui/__snapshots__/Box.test.js.snap b/__tests__/ui/__snapshots__/Box.test.js.snap index 746ec0960..bf0070d3a 100644 --- a/__tests__/ui/__snapshots__/Box.test.js.snap +++ b/__tests__/ui/__snapshots__/Box.test.js.snap @@ -2,7 +2,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
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__/Button.test.js.snap b/__tests__/ui/__snapshots__/Button.test.js.snap index 65a604fb3..91d73e7dd 100644 --- a/__tests__/ui/__snapshots__/Button.test.js.snap +++ b/__tests__/ui/__snapshots__/Button.test.js.snap @@ -142,18 +142,18 @@ exports[`matches enzyme snapshots matches snapshot 1`] = ` exports[`matches test renderer snapshot serializes the styles 1`] = `
test
`; diff --git a/__tests__/ui/__snapshots__/Center.test.js.snap b/__tests__/ui/__snapshots__/Center.test.js.snap index 8d94fc4ee..2262d41b1 100644 --- a/__tests__/ui/__snapshots__/Center.test.js.snap +++ b/__tests__/ui/__snapshots__/Center.test.js.snap @@ -10,10 +10,10 @@ exports[`matches enzyme snapshots matches snapshot 1`] = ` exports[`matches test renderer snapshot serializes the styles 1`] = `
Test
diff --git a/__tests__/ui/__snapshots__/Column.test.js.snap b/__tests__/ui/__snapshots__/Column.test.js.snap index 7d0232f64..6e25163ab 100644 --- a/__tests__/ui/__snapshots__/Column.test.js.snap +++ b/__tests__/ui/__snapshots__/Column.test.js.snap @@ -2,7 +2,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
Test @@ -15,15 +15,15 @@ exports[`matches enzyme snapshots matches snapshot 1`] = ` exports[`matches test renderer snapshot serializes the styles 1`] = `
Test
Test
diff --git a/__tests__/ui/__snapshots__/Container.test.js.snap b/__tests__/ui/__snapshots__/Container.test.js.snap index b8ab90893..ee8a44443 100644 --- a/__tests__/ui/__snapshots__/Container.test.js.snap +++ b/__tests__/ui/__snapshots__/Container.test.js.snap @@ -17,7 +17,7 @@ exports[`matches test renderer snapshot serializes the styles 1`] = ` className="TestContainer Container" >
Test
diff --git a/__tests__/ui/__snapshots__/Content.test.js.snap b/__tests__/ui/__snapshots__/Content.test.js.snap index 98f1bd7b5..bd7809e83 100644 --- a/__tests__/ui/__snapshots__/Content.test.js.snap +++ b/__tests__/ui/__snapshots__/Content.test.js.snap @@ -2,7 +2,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = `
test

", @@ -13,7 +13,7 @@ exports[`matches enzyme snapshots matches snapshot 1`] = ` exports[`matches test renderer snapshot serializes the styles 1`] = `
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`] = `
Test Copy
`; 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`] = `
Previous
@@ -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,