You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ju...@apache.org on 2023/08/24 23:54:14 UTC
[superset] branch master updated: chore(sqllab): typescript for getInitialState (#25047)
This is an automated email from the ASF dual-hosted git repository.
justinpark pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 4d9e9a4b1c chore(sqllab): typescript for getInitialState (#25047)
4d9e9a4b1c is described below
commit 4d9e9a4b1c605d6ce98a99f4d8084fb920874c20
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Thu Aug 24 16:54:06 2023 -0700
chore(sqllab): typescript for getInitialState (#25047)
---
.../src/SqlLab/components/SouthPane/index.tsx | 2 +-
.../src/SqlLab/components/SqlEditor/index.jsx | 2 +-
.../SqlLab/components/SqlEditorLeftBar/index.tsx | 3 +-
.../SqlLab/components/SqlEditorTabHeader/index.tsx | 2 +-
.../src/SqlLab/components/TableElement/index.tsx | 11 +--
.../src/SqlLab/hooks/useQueryEditor/index.ts | 2 +-
.../src/SqlLab/reducers/getInitialState.test.ts | 108 +++++++++++++++++----
.../{getInitialState.js => getInitialState.ts} | 61 ++++++------
superset-frontend/src/SqlLab/types.ts | 22 ++++-
.../src/hooks/apiResources/queryApi.ts | 1 +
.../src/hooks/apiResources/sqlLab.test.ts | 100 +++++++++++++++++++
superset-frontend/src/hooks/apiResources/sqlLab.ts | 77 +++++++++++++++
12 files changed, 324 insertions(+), 67 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
index 4e38a76f9c..42ee4830fd 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
@@ -117,7 +117,7 @@ const SouthPane = ({
queries[dataPreviewQueryId],
)
.map(({ name, dataPreviewQueryId }) => ({
- ...queries[dataPreviewQueryId],
+ ...queries[dataPreviewQueryId || ''],
tableName: name,
}));
const editorQueries = Object.values(queries).filter(
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index cda252f1e3..2626d70d73 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -225,7 +225,7 @@ const SqlEditor = ({
const { database, latestQuery, hideLeftBar } = useSelector(
({ sqlLab: { unsavedQueryEditor, databases, queries } }) => {
let { dbId, latestQueryId, hideLeftBar } = queryEditor;
- if (unsavedQueryEditor.id === queryEditor.id) {
+ if (unsavedQueryEditor?.id === queryEditor.id) {
dbId = unsavedQueryEditor.dbId || dbId;
latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
hideLeftBar = unsavedQueryEditor.hideLeftBar || hideLeftBar;
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
index e89bdc57b7..169acd82ce 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
@@ -27,6 +27,7 @@ import React, {
import { useDispatch } from 'react-redux';
import querystring from 'query-string';
+import { Table } from 'src/SqlLab/types';
import {
queryEditorSetDb,
addTable,
@@ -52,7 +53,7 @@ import {
LocalStorageKeys,
setItem,
} from 'src/utils/localStorageHelpers';
-import TableElement, { Table } from '../TableElement';
+import TableElement from '../TableElement';
interface ExtendedTable extends Table {
expanded: boolean;
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
index 1e1b22a81d..487af451a5 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/index.tsx
@@ -55,7 +55,7 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
const qe = useSelector<SqlLabRootState, QueryEditor>(
({ sqlLab: { unsavedQueryEditor } }) => ({
...queryEditor,
- ...(queryEditor.id === unsavedQueryEditor.id && unsavedQueryEditor),
+ ...(queryEditor.id === unsavedQueryEditor?.id && unsavedQueryEditor),
}),
shallowEqual,
);
diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx
index 0559307fdf..b003655634 100644
--- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx
+++ b/superset-frontend/src/SqlLab/components/TableElement/index.tsx
@@ -18,6 +18,7 @@
*/
import React, { useState, useRef, useEffect } from 'react';
import { useDispatch } from 'react-redux';
+import type { Table } from 'src/SqlLab/types';
import Collapse from 'src/components/Collapse';
import Card from 'src/components/Card';
import ButtonGroup from 'src/components/ButtonGroup';
@@ -49,16 +50,6 @@ export interface Column {
type: string;
}
-export interface Table {
- id: string;
- dbId: number;
- schema: string;
- name: string;
- dataPreviewQueryId?: string | null;
- expanded?: boolean;
- initialized?: boolean;
-}
-
export interface TableElementProps {
table: Table;
}
diff --git a/superset-frontend/src/SqlLab/hooks/useQueryEditor/index.ts b/superset-frontend/src/SqlLab/hooks/useQueryEditor/index.ts
index 7044e77798..89301716c0 100644
--- a/superset-frontend/src/SqlLab/hooks/useQueryEditor/index.ts
+++ b/superset-frontend/src/SqlLab/hooks/useQueryEditor/index.ts
@@ -29,7 +29,7 @@ export default function useQueryEditor<T extends keyof QueryEditor>(
pick(
{
...queryEditors.find(({ id }) => id === sqlEditorId),
- ...(sqlEditorId === unsavedQueryEditor.id && unsavedQueryEditor),
+ ...(sqlEditorId === unsavedQueryEditor?.id && unsavedQueryEditor),
},
['id'].concat(attributes),
) as Pick<QueryEditor, T | 'id'>,
diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts b/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
index af074c3cc4..c4f064f650 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
@@ -17,37 +17,49 @@
* under the License.
*/
+import { DEFAULT_COMMON_BOOTSTRAP_DATA } from 'src/constants';
import getInitialState, { dedupeTabHistory } from './getInitialState';
const apiData = {
- common: {
- conf: {
- DEFAULT_SQLLAB_LIMIT: 1,
- },
- },
- active_tab: null,
+ common: DEFAULT_COMMON_BOOTSTRAP_DATA,
tab_state_ids: [],
databases: [],
- queries: [],
- requested_query: null,
+ queries: {},
user: {
userId: 1,
username: 'some name',
+ isActive: true,
+ isAnonymous: false,
+ firstName: 'first name',
+ lastName: 'last name',
+ permissions: {},
+ roles: {},
},
};
const apiDataWithTabState = {
...apiData,
- tab_state_ids: [{ id: 1 }],
- active_tab: { id: 1, table_schemas: [] },
+ tab_state_ids: [{ id: 1, label: 'test' }],
+ active_tab: {
+ id: 1,
+ user_id: 1,
+ label: 'editor1',
+ active: true,
+ database_id: 1,
+ sql: '',
+ table_schemas: [],
+ saved_query: null,
+ template_params: null,
+ latest_query: null,
+ },
};
describe('getInitialState', () => {
it('should output the user that is passed in', () => {
- expect(getInitialState(apiData).user.userId).toEqual(1);
+ expect(getInitialState(apiData).user?.userId).toEqual(1);
});
it('should return undefined instead of null for templateParams', () => {
expect(
- getInitialState(apiDataWithTabState).sqlLab.queryEditors[0]
- .templateParams,
+ getInitialState(apiDataWithTabState).sqlLab?.queryEditors?.[0]
+ ?.templateParams,
).toBeUndefined();
});
@@ -55,13 +67,65 @@ describe('getInitialState', () => {
it('should dedupe the tab history', () => {
[
{ value: [], expected: [] },
- { value: [12, 3, 4, 5, 6], expected: [12, 3, 4, 5, 6] },
- { value: [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], expected: [1, 2] },
{
- value: [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3],
- expected: [1, 2, 3],
+ value: ['12', '3', '4', '5', '6'],
+ expected: ['12', '3', '4', '5', '6'],
+ },
+ {
+ value: [
+ '1',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ ],
+ expected: ['1', '2'],
+ },
+ {
+ value: [
+ '1',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '3',
+ ],
+ expected: ['1', '2', '3'],
+ },
+ {
+ value: [
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '2',
+ '3',
+ ],
+ expected: ['2', '3'],
},
- { value: [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3], expected: [2, 3] },
].forEach(({ value, expected }) => {
expect(dedupeTabHistory(value)).toEqual(expected);
});
@@ -92,6 +156,11 @@ describe('getInitialState', () => {
...apiData,
active_tab: {
id: 1,
+ user_id: 1,
+ label: 'editor1',
+ active: true,
+ database_id: 1,
+ sql: '',
table_schemas: [
{
id: 1,
@@ -116,6 +185,9 @@ describe('getInitialState', () => {
},
},
],
+ saved_query: null,
+ template_params: null,
+ latest_query: null,
},
}).sqlLab.tables;
expect(initializedTables.map(({ id }) => id)).toEqual([1, 2, 6]);
diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
similarity index 82%
rename from superset-frontend/src/SqlLab/reducers/getInitialState.js
rename to superset-frontend/src/SqlLab/reducers/getInitialState.ts
index 5bc225824c..24db593530 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.js
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
@@ -18,9 +18,17 @@
*/
import { t } from '@superset-ui/core';
import getToastsFromPyFlashMessages from 'src/components/MessageToasts/getToastsFromPyFlashMessages';
+import type { BootstrapData } from 'src/types/bootstrapTypes';
+import type { InitialState } from 'src/hooks/apiResources/sqlLab';
+import type {
+ QueryEditor,
+ UnsavedQueryEditor,
+ SqlLabRootState,
+ Table,
+} from 'src/SqlLab/types';
-export function dedupeTabHistory(tabHistory) {
- return tabHistory.reduce(
+export function dedupeTabHistory(tabHistory: string[]) {
+ return tabHistory.reduce<string[]>(
(result, tabId) =>
result.slice(-1)[0] === tabId ? result : result.concat(tabId),
[],
@@ -34,7 +42,7 @@ export default function getInitialState({
databases,
queries: queries_,
user,
-}) {
+}: BootstrapData & Partial<InitialState>) {
/**
* Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the
* browser's localStorage. The feature flag `SQLLAB_BACKEND_PERSISTENCE`
@@ -43,54 +51,42 @@ export default function getInitialState({
* To allow for a transparent migration, the initial state is a combination
* of the backend state (if any) with the browser state (if any).
*/
- let queryEditors = {};
+ let queryEditors: Record<string, QueryEditor> = {};
const defaultQueryEditor = {
- id: null,
loaded: true,
name: t('Untitled query'),
sql: 'SELECT *\nFROM\nWHERE',
- selectedText: null,
latestQueryId: null,
autorun: false,
- templateParams: null,
dbId: common.conf.SQLLAB_DEFAULT_DBID,
queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT,
- validationResult: {
- id: null,
- errors: [],
- completed: false,
- },
hideLeftBar: false,
+ remoteId: null,
};
- let unsavedQueryEditor = {};
+ let unsavedQueryEditor: UnsavedQueryEditor = {};
/**
* Load state from the backend. This will be empty if the feature flag
* `SQLLAB_BACKEND_PERSISTENCE` is off.
*/
tabStateIds.forEach(({ id, label }) => {
- let queryEditor;
+ let queryEditor: QueryEditor;
if (activeTab && activeTab.id === id) {
queryEditor = {
id: id.toString(),
loaded: true,
name: activeTab.label,
- sql: activeTab.sql || undefined,
+ sql: activeTab.sql || '',
selectedText: undefined,
latestQueryId: activeTab.latest_query
? activeTab.latest_query.id
: null,
- remoteId: activeTab.saved_query?.id,
- autorun: activeTab.autorun,
+ remoteId: activeTab.saved_query?.id || null,
+ autorun: Boolean(activeTab.autorun),
templateParams: activeTab.template_params || undefined,
dbId: activeTab.database_id,
schema: activeTab.schema,
queryLimit: activeTab.query_limit,
- validationResult: {
- id: null,
- errors: [],
- completed: false,
- },
hideLeftBar: activeTab.hide_left_bar,
};
} else {
@@ -109,7 +105,8 @@ export default function getInitialState({
});
const tabHistory = activeTab ? [activeTab.id.toString()] : [];
- let tables = {};
+ let tables = {} as Record<string, Table>;
+ const editorTabLastUpdatedAt = Date.now();
if (activeTab) {
activeTab.table_schemas
.filter(tableSchema => tableSchema.description !== null)
@@ -140,17 +137,18 @@ export default function getInitialState({
* hasn't used SQL Lab after it has been turned on, the state will be stored
* in the browser's local storage.
*/
- if (
- localStorage.getItem('redux') &&
- JSON.parse(localStorage.getItem('redux')).sqlLab
- ) {
- const { sqlLab } = JSON.parse(localStorage.getItem('redux'));
+ const localStorageData = localStorage.getItem('redux');
+ const sqlLabCacheData = localStorageData
+ ? (JSON.parse(localStorageData) as Pick<SqlLabRootState, 'sqlLab'>)
+ : undefined;
+ if (localStorageData && sqlLabCacheData?.sqlLab) {
+ const { sqlLab } = sqlLabCacheData;
if (sqlLab.queryEditors.length === 0) {
// migration was successful
localStorage.removeItem('redux');
} else {
- unsavedQueryEditor = sqlLab.unsavedQueryEditor || {};
+ unsavedQueryEditor = sqlLab.unsavedQueryEditor || unsavedQueryEditor;
// add query editors and tables to state with a special flag so they can
// be migrated if the `SQLLAB_BACKEND_PERSISTENCE` feature flag is on
sqlLab.queryEditors.forEach(qe => {
@@ -199,11 +197,12 @@ export default function getInitialState({
tabHistory: dedupeTabHistory(tabHistory),
tables: Object.values(tables),
queriesLastUpdate: Date.now(),
- unsavedQueryEditor,
+ editorTabLastUpdatedAt,
queryCostEstimates: {},
+ unsavedQueryEditor,
},
messageToasts: getToastsFromPyFlashMessages(
- (common || {}).flash_messages || [],
+ (common || {})?.flash_messages || [],
),
localStorageUsageInKilobytes: 0,
common: {
diff --git a/superset-frontend/src/SqlLab/types.ts b/superset-frontend/src/SqlLab/types.ts
index eb0d89a98d..d8a78a858d 100644
--- a/superset-frontend/src/SqlLab/types.ts
+++ b/superset-frontend/src/SqlLab/types.ts
@@ -33,7 +33,8 @@ export interface QueryEditor {
id: string;
dbId?: number;
name: string;
- schema: string;
+ title?: string; // keep it optional for backward compatibility
+ schema?: string;
autorun: boolean;
sql: string;
remoteId: number | null;
@@ -43,6 +44,8 @@ export interface QueryEditor {
selectedText?: string;
queryLimit?: number;
description?: string;
+ loaded?: boolean;
+ inLocalStorage?: boolean;
}
export type toastState = {
@@ -53,6 +56,19 @@ export type toastState = {
noDuplicate: boolean;
};
+export type UnsavedQueryEditor = Partial<QueryEditor>;
+
+export interface Table {
+ id: string;
+ dbId: number;
+ schema: string;
+ name: string;
+ queryEditorId: QueryEditor['id'];
+ dataPreviewQueryId: string | null;
+ expanded?: boolean;
+ initialized?: boolean;
+}
+
export type SqlLabRootState = {
sqlLab: {
activeSouthPaneTab: string | number; // default is string; action.newQuery.id is number
@@ -63,10 +79,10 @@ export type SqlLabRootState = {
queries: Record<string, QueryResponse>;
queryEditors: QueryEditor[];
tabHistory: string[]; // default is activeTab ? [activeTab.id.toString()] : []
- tables: Record<string, any>[];
+ tables: Table[];
queriesLastUpdate: number;
errorMessage: string | null;
- unsavedQueryEditor: Partial<QueryEditor>;
+ unsavedQueryEditor: UnsavedQueryEditor;
queryCostEstimates?: Record<string, QueryCostEstimate>;
editorTabLastUpdatedAt?: number;
};
diff --git a/superset-frontend/src/hooks/apiResources/queryApi.ts b/superset-frontend/src/hooks/apiResources/queryApi.ts
index 2881fc2252..3461c8443e 100644
--- a/superset-frontend/src/hooks/apiResources/queryApi.ts
+++ b/superset-frontend/src/hooks/apiResources/queryApi.ts
@@ -71,6 +71,7 @@ export const api = createApi({
'DatabaseFunctions',
'QueryValidations',
'TableMetadatas',
+ 'SqlLabInitialState',
],
endpoints: () => ({}),
baseQuery: supersetClientQuery,
diff --git a/superset-frontend/src/hooks/apiResources/sqlLab.test.ts b/superset-frontend/src/hooks/apiResources/sqlLab.test.ts
new file mode 100644
index 0000000000..31c7a68b44
--- /dev/null
+++ b/superset-frontend/src/hooks/apiResources/sqlLab.test.ts
@@ -0,0 +1,100 @@
+/**
+ * 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 { act, renderHook } from '@testing-library/react-hooks';
+import {
+ createWrapper,
+ defaultStore as store,
+} from 'spec/helpers/testing-library';
+import { api } from 'src/hooks/apiResources/queryApi';
+import { DEFAULT_COMMON_BOOTSTRAP_DATA } from 'src/constants';
+
+import { useSqlLabInitialState } from './sqlLab';
+
+const fakeApiResult = {
+ result: {
+ common: DEFAULT_COMMON_BOOTSTRAP_DATA,
+ tab_state_ids: [],
+ databases: [],
+ queries: {},
+ user: {
+ userId: 1,
+ username: 'some name',
+ isActive: true,
+ isAnonymous: false,
+ firstName: 'first name',
+ lastName: 'last name',
+ permissions: {},
+ roles: {},
+ },
+ },
+};
+
+const expectedResult = fakeApiResult.result;
+const sqlLabInitialStateApiRoute = `glob:*/api/v1/sqllab/`;
+
+afterEach(() => {
+ fetchMock.reset();
+ act(() => {
+ store.dispatch(api.util.resetApiState());
+ });
+});
+
+beforeEach(() => {
+ fetchMock.get(sqlLabInitialStateApiRoute, fakeApiResult);
+});
+
+test('returns api response mapping json result', async () => {
+ const { result, waitFor } = renderHook(() => useSqlLabInitialState(), {
+ wrapper: createWrapper({
+ useRedux: true,
+ store,
+ }),
+ });
+ await waitFor(() =>
+ expect(fetchMock.calls(sqlLabInitialStateApiRoute).length).toBe(1),
+ );
+ expect(result.current.data).toEqual(expectedResult);
+ expect(fetchMock.calls(sqlLabInitialStateApiRoute).length).toBe(1);
+ // clean up cache
+ act(() => {
+ store.dispatch(api.util.invalidateTags(['SqlLabInitialState']));
+ });
+ await waitFor(() =>
+ expect(fetchMock.calls(sqlLabInitialStateApiRoute).length).toBe(2),
+ );
+ expect(result.current.data).toEqual(expectedResult);
+});
+
+test('returns cached data without api request', async () => {
+ const { result, waitFor, rerender } = renderHook(
+ () => useSqlLabInitialState(),
+ {
+ wrapper: createWrapper({
+ store,
+ useRedux: true,
+ }),
+ },
+ );
+ await waitFor(() => expect(result.current.data).toEqual(expectedResult));
+ expect(fetchMock.calls(sqlLabInitialStateApiRoute).length).toBe(1);
+ rerender();
+ await waitFor(() => expect(result.current.data).toEqual(expectedResult));
+ expect(fetchMock.calls(sqlLabInitialStateApiRoute).length).toBe(1);
+});
diff --git a/superset-frontend/src/hooks/apiResources/sqlLab.ts b/superset-frontend/src/hooks/apiResources/sqlLab.ts
new file mode 100644
index 0000000000..dac53043c3
--- /dev/null
+++ b/superset-frontend/src/hooks/apiResources/sqlLab.ts
@@ -0,0 +1,77 @@
+/**
+ * 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 type { QueryResponse } from '@superset-ui/core';
+import type { JsonResponse } from './queryApi';
+import { api } from './queryApi';
+
+export type InitialState = {
+ active_tab: {
+ id: number;
+ user_id: number;
+ label: string;
+ active: boolean;
+ database_id: number;
+ schema?: string;
+ table_schemas: {
+ id: number;
+ table: string;
+ description: {
+ columns?: {
+ name: string;
+ type: string;
+ }[];
+ dataPreviewQueryId?: string;
+ } & Record<string, any>;
+ schema?: string;
+ tab_state_id: number;
+ database_id?: number;
+ expanded?: boolean;
+ }[];
+ sql: string;
+ query_limit?: number;
+ latest_query: QueryResponse | null;
+ autorun?: boolean;
+ template_params: string | null;
+ hide_left_bar?: boolean;
+ saved_query: { id: number } | null;
+ extra_json?: object;
+ };
+ databases: object[];
+ queries: Record<string, QueryResponse & { inLocalStorage?: boolean }>;
+ tab_state_ids: {
+ id: number;
+ label: string;
+ }[];
+};
+
+const queryValidationApi = api.injectEndpoints({
+ endpoints: builder => ({
+ sqlLabInitialState: builder.query<InitialState, void>({
+ providesTags: ['SqlLabInitialState'],
+ query: () => ({
+ endpoint: `/api/v1/sqllab/`,
+ headers: { 'Content-Type': 'application/json' },
+ transformResponse: ({ json }: JsonResponse) => json.result,
+ }),
+ }),
+ }),
+});
+
+export const { useSqlLabInitialStateQuery: useSqlLabInitialState } =
+ queryValidationApi;