diff --git a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx index 16816c9e3a7b3..e2d0453bb297a 100644 --- a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx @@ -24,7 +24,13 @@ import QueryTable from 'src/SqlLab/components/QueryTable'; interface QueryHistoryProps { queries: Query[]; - actions: Record; + actions: { + queryEditorSetSql: Function; + cloneQueryToNewTab: Function; + fetchQueryResults: Function; + clearQueryResults: Function; + removeQuery: Function; + }; displayLimit: number; } diff --git a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx b/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx index c5b1d47e16154..ae2562207e2b3 100644 --- a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx +++ b/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx @@ -37,6 +37,11 @@ interface QuerySearchProps { actions: { addDangerToast: (msg: string) => void; setDatabases: (data: Record) => Record; + queryEditorSetSql: Function; + cloneQueryToNewTab: Function; + fetchQueryResults: Function; + clearQueryResults: Function; + removeQuery: Function; }; displayLimit: number; } diff --git a/superset-frontend/src/SqlLab/components/QueryTable/QueryTable.test.jsx b/superset-frontend/src/SqlLab/components/QueryTable/QueryTable.test.jsx index 3dc619a288ee3..5be5a384863f9 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/QueryTable.test.jsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/QueryTable.test.jsx @@ -25,11 +25,13 @@ import TableView from 'src/components/TableView'; import TableCollection from 'src/components/TableCollection'; import { Provider } from 'react-redux'; import { queries, user } from 'src/SqlLab/fixtures'; +import * as actions from 'src/SqlLab/actions/sqlLab'; describe('QueryTable', () => { const mockedProps = { queries, displayLimit: 100, + actions, }; it('is valid', () => { expect(React.isValidElement()).toBe(true); diff --git a/superset-frontend/src/SqlLab/components/QueryTable/index.jsx b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx similarity index 57% rename from superset-frontend/src/SqlLab/components/QueryTable/index.jsx rename to superset-frontend/src/SqlLab/components/QueryTable/index.tsx index 596cbe257f4e6..142d8a13099da 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/index.jsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx @@ -17,7 +17,6 @@ * under the License. */ import React, { useMemo } from 'react'; -import PropTypes from 'prop-types'; import moment from 'moment'; import Card from 'src/components/Card'; import ProgressBar from 'src/components/ProgressBar'; @@ -29,133 +28,151 @@ import Button from 'src/components/Button'; import { fDuration } from 'src/modules/dates'; import Icons from 'src/components/Icons'; import { Tooltip } from 'src/components/Tooltip'; +import { Query, RootState } from 'src/SqlLab/types'; +import ModalTrigger from 'src/components/ModalTrigger'; +import { UserWithPermissionsAndRoles as User } from 'src/types/bootstrapTypes'; import ResultSet from '../ResultSet'; -import ModalTrigger from '../../../components/ModalTrigger'; import HighlightedSql from '../HighlightedSql'; import { StaticPosition, verticalAlign, StyledTooltip } from './styles'; -const propTypes = { - columns: PropTypes.array, - actions: PropTypes.object, - queries: PropTypes.array, - onUserClicked: PropTypes.func, - onDbClicked: PropTypes.func, - displayLimit: PropTypes.number.isRequired, -}; -const defaultProps = { - columns: ['started', 'duration', 'rows'], - queries: [], - onUserClicked: () => {}, - onDbClicked: () => {}, -}; +interface QueryTableQuery extends Omit { + state?: Record; + sql?: Record; + progress?: Record; +} + +interface QueryTableProps { + columns?: string[]; + actions: { + queryEditorSetSql: Function; + cloneQueryToNewTab: Function; + fetchQueryResults: Function; + clearQueryResults: Function; + removeQuery: Function; + }; + queries?: Query[]; + onUserClicked?: Function; + onDbClicked?: Function; + displayLimit: number; +} -const openQuery = id => { +const openQuery = (id: number) => { const url = `/superset/sqllab?queryId=${id}`; window.open(url); }; -const QueryTable = props => { +const QueryTable = ({ + columns = ['started', 'duration', 'rows'], + actions, + queries = [], + onUserClicked = () => undefined, + onDbClicked = () => undefined, + displayLimit, +}: QueryTableProps) => { const theme = useTheme(); - const statusAttributes = { - success: { - config: { - icon: , - label: t('Success'), - }, - }, - failed: { - config: { - icon: , - label: t('Failed'), - }, - }, - stopped: { - config: { - icon: , - label: t('Failed'), - }, - }, - running: { - config: { - icon: , - label: t('Running'), - }, - }, - fetching: { - config: { - icon: , - label: t('fetching'), - }, - }, - timed_out: { - config: { - icon: , - label: t('Offline'), - }, - }, - scheduled: { - config: { - icon: , - label: t('Scheduled'), - }, - }, - pending: { - config: { - icon: , - label: t('Scheduled'), - }, - }, - error: { - config: { - icon: , - label: t('Unknown Status'), - }, - }, - }; - const setHeaders = column => { + const setHeaders = (column: string) => { if (column === 'sql') { return column.toUpperCase(); } return column.charAt(0).toUpperCase().concat(column.slice(1)); }; - const columns = useMemo( + const columnsOfTable = useMemo( () => - props.columns.map(column => ({ + columns.map(column => ({ accessor: column, Header: () => setHeaders(column), disableSortBy: true, })), - [props.columns], + [columns], ); - const user = useSelector(({ sqlLab: { user } }) => user); + const user = useSelector(state => state.sqlLab.user); - const data = useMemo(() => { - const restoreSql = query => { - props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql); - }; + const { + queryEditorSetSql, + cloneQueryToNewTab, + fetchQueryResults, + clearQueryResults, + removeQuery, + } = actions; - const openQueryInNewTab = query => { - props.actions.cloneQueryToNewTab(query, true); + const data = useMemo(() => { + const restoreSql = (query: Query) => { + queryEditorSetSql({ id: query.sqlEditorId }, query.sql); }; - const openAsyncResults = (query, displayLimit) => { - props.actions.fetchQueryResults(query, displayLimit); + const openQueryInNewTab = (query: Query) => { + cloneQueryToNewTab(query, true); }; - const clearQueryResults = query => { - props.actions.clearQueryResults(query); + const openAsyncResults = (query: Query, displayLimit: number) => { + fetchQueryResults(query, displayLimit); }; - const removeQuery = query => { - props.actions.removeQuery(query); + const statusAttributes = { + success: { + config: { + icon: , + label: t('Success'), + }, + }, + failed: { + config: { + icon: , + label: t('Failed'), + }, + }, + stopped: { + config: { + icon: , + label: t('Failed'), + }, + }, + running: { + config: { + icon: , + label: t('Running'), + }, + }, + fetching: { + config: { + icon: , + label: t('Fetching'), + }, + }, + timed_out: { + config: { + icon: , + label: t('Offline'), + }, + }, + scheduled: { + config: { + icon: , + label: t('Scheduled'), + }, + }, + pending: { + config: { + icon: , + label: t('Scheduled'), + }, + }, + error: { + config: { + icon: , + label: t('Unknown Status'), + }, + }, }; - return props.queries + return queries .map(query => { - const q = { ...query }; - const status = statusAttributes[q.state] || statusAttributes.error; + const { state, sql, progress, ...rest } = query; + const q = rest as QueryTableQuery; + + const status = statusAttributes[state] || statusAttributes.error; if (q.endDttm) { q.duration = fDuration(q.startDttm, q.endDttm); @@ -172,7 +189,7 @@ const QueryTable = props => { @@ -181,7 +198,7 @@ const QueryTable = props => { @@ -200,7 +217,7 @@ const QueryTable = props => { q.sql = ( { } modalTitle={t('Data preview')} - beforeOpen={() => openAsyncResults(query, props.displayLimit)} + beforeOpen={() => openAsyncResults(query, displayLimit)} onExit={() => clearQueryResults(query)} modalBody={ } responsive @@ -240,17 +258,14 @@ const QueryTable = props => { q.output = [schemaUsed, q.tempTable].filter(v => v).join('.'); } q.progress = - q.state === 'success' ? ( + state === 'success' ? ( ) : ( - + ); q.state = ( @@ -266,35 +281,44 @@ const QueryTable = props => { )} placement="top" > - + openQueryInNewTab(query)} tooltip={t('Run query in a new tab')} placement="top" > - + removeQuery(query)} > - + ); return q; }) .reverse(); - }, [props]); + }, [ + queries, + onUserClicked, + onDbClicked, + user, + displayLimit, + actions, + clearQueryResults, + cloneQueryToNewTab, + fetchQueryResults, + queryEditorSetSql, + removeQuery, + ]); return (
{ ); }; -QueryTable.propTypes = propTypes; -QueryTable.defaultProps = defaultProps; - export default QueryTable; diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx index 80f9e2d7985c4..f7efc04f34725 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx @@ -44,7 +44,14 @@ interface SouthPanePropTypes { editorQueries: any[]; latestQueryId?: string; dataPreviewQueries: any[]; - actions: Record; + actions: { + queryEditorSetSql: Function; + cloneQueryToNewTab: Function; + fetchQueryResults: Function; + clearQueryResults: Function; + removeQuery: Function; + setActiveSouthPaneTab: Function; + }; activeSouthPaneTab?: string; height: number; databases: Record; diff --git a/superset-frontend/src/SqlLab/types.ts b/superset-frontend/src/SqlLab/types.ts index 1308f58bfdb7c..27f08f5bbed18 100644 --- a/superset-frontend/src/SqlLab/types.ts +++ b/superset-frontend/src/SqlLab/types.ts @@ -18,6 +18,8 @@ */ import { SupersetError } from 'src/components/ErrorMessage/types'; import { CtasEnum } from 'src/SqlLab/actions/sqlLab'; +import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; +import { ToastType } from 'src/components/MessageToasts/types'; export type Column = { name: string; @@ -68,6 +70,19 @@ export type Query = { rows: number; queryLimit: number; limitingFactor: string; + endDttm: number; + duration: string; + startDttm: number; + time: Record; + user: Record; + userId: number; + db: Record; + started: string; + querylink: Record; + queryId: number; + executedSql: string; + output: string | Record; + actions: Record; }; export interface QueryEditor { @@ -82,3 +97,29 @@ export interface QueryEditor { errors: SupersetError[]; }; } + +export type toastState = { + id: string; + toastType: ToastType; + text: string; + duration: number; + noDuplicate: boolean; +}; + +export type RootState = { + sqlLab: { + activeSouthPaneTab: string | number; // default is string; action.newQuery.id is number + alerts: any[]; + databases: Record; + offline: boolean; + queries: Query[]; + queryEditors: QueryEditor[]; + tabHistory: string[]; // default is activeTab ? [activeTab.id.toString()] : [] + tables: Record[]; + queriesLastUpdate: number; + user: UserWithPermissionsAndRoles; + }; + localStorageUsageInKilobytes: number; + messageToasts: toastState[]; + common: {}; +}; diff --git a/superset-frontend/src/components/Icons/IconType.ts b/superset-frontend/src/components/Icons/IconType.ts index 4687af6bc9835..7371007bf2bc3 100644 --- a/superset-frontend/src/components/Icons/IconType.ts +++ b/superset-frontend/src/components/Icons/IconType.ts @@ -22,7 +22,7 @@ type AntdIconType = IconComponentProps; type IconType = AntdIconType & { iconColor?: string; twoToneColor?: string; - iconSize?: 's' | 'm' | 'l' | 'xl' | 'xxl'; + iconSize?: 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; }; export default IconType;