/** * 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 { useMemo, ReactNode, useState, useRef } from 'react'; import { Card, Button, Tooltip, Label, Icons, ModalTrigger, TableView, } from '@superset-ui/core/components'; import ProgressBar from '@superset-ui/core/components/ProgressBar'; import { t } from '@apache-superset/core/translation'; import { QueryResponse, QueryState } from '@superset-ui/core'; import { useTheme } from '@apache-superset/core/theme'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { queryEditorSetSql, cloneQueryToNewTab, fetchQueryResults, clearQueryResults, removeQuery, startQuery, } from 'src/SqlLab/actions/sqlLab'; import { fDuration, extendedDayjs } from '@superset-ui/core/utils/dates'; import { SqlLabRootState } from 'src/SqlLab/types'; import { UserWithPermissionsAndRoles as User } from 'src/types/bootstrapTypes'; import { makeUrl } from 'src/utils/pathUtils'; import ResultSet from '../ResultSet'; import HighlightedSql from '../HighlightedSql'; import { StaticPosition, StyledTooltip, ModalResultSetWrapper } from './styles'; interface QueryTableQuery extends Omit< QueryResponse, | 'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started' | 'user' | 'db' > { state?: ReactNode; sql?: ReactNode; progress?: ReactNode; results?: ReactNode; duration?: ReactNode; started?: ReactNode; user?: ReactNode; db?: ReactNode; } interface QueryTableProps { columns?: string[]; queries?: QueryResponse[]; onUserClicked?: Function; onDbClicked?: Function; displayLimit: number; latestQueryId?: string | undefined; } const openQuery = (id: number) => { const url = makeUrl(`/sqllab?queryId=${id}`); window.open(url); }; const QueryTable = ({ columns = ['started', 'duration', 'rows'], queries = [], onUserClicked = () => undefined, onDbClicked = () => undefined, displayLimit, latestQueryId, }: QueryTableProps) => { const theme = useTheme(); const dispatch = useDispatch(); const [selectedQuery, setSelectedQuery] = useState( null, ); const selectedQueryRef = useRef(null); const modalRef = useRef<{ close: () => void; open: (e: React.MouseEvent) => void; showModal: boolean; } | null>(null); const QUERY_HISTORY_TABLE_HEADERS_LOCALIZED = { state: t('State'), started: t('Started'), duration: t('Duration'), progress: t('Progress'), rows: t('Rows'), sql: t('SQL'), results: t('Results'), actions: t('Actions'), }; const setHeaders = (column: string) => { if (column === 'sql') { return column.toUpperCase(); } return column.charAt(0).toUpperCase().concat(column.slice(1)); }; const columnsOfTable = useMemo( () => columns.map(column => ({ accessor: column, Header: QUERY_HISTORY_TABLE_HEADERS_LOCALIZED[ column as keyof typeof QUERY_HISTORY_TABLE_HEADERS_LOCALIZED ] || setHeaders(column), disableSortBy: true, id: column, })), [columns], ); const user = useSelector(state => state.user); const reduxQueries = useSelector< SqlLabRootState, Record >(state => state.sqlLab?.queries ?? {}, shallowEqual); const openAsyncResults = (query: QueryResponse, displayLimit: number) => { dispatch(fetchQueryResults(query, displayLimit)); }; const data = useMemo(() => { const restoreSql = (query: QueryResponse) => { dispatch( queryEditorSetSql({ id: query.sqlEditorId }, query.sql, query.id), ); }; const openQueryInNewTab = (query: QueryResponse) => { dispatch(cloneQueryToNewTab(query, true)); }; const statusAttributes = { success: { config: { icon: ( ), // 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'), }, }, started: { config: { icon: ( ), label: t('Started'), }, }, }; return queries .map(query => { const { state, sql, progress, results: _results, ...rest } = query; const q = rest as QueryTableQuery; const status = statusAttributes[state] || statusAttributes.error; if (q.endDttm) { q.duration = ( ); } q.user = ( ); q.db = ( ); q.started = ( ); q.querylink = ( ); q.sql = ( ); if (q.resultsKey) { q.results = ( ); } else { q.results = <>; } q.progress = state === 'success' ? ( ) : ( ); q.state = ( {status.config.icon} ); q.actions = (
restoreSql(query)} tooltip={t( 'Overwrite text in the editor with a query on this table', )} placement="top" className="pointer" > openQueryInNewTab(query)} tooltip={t('Run query in a new tab')} placement="top" className="pointer" > {q.id !== latestQueryId && ( dispatch(removeQuery(query))} className="pointer" > )}
); return q; }) .reverse(); }, [queries, onUserClicked, onDbClicked, user, displayLimit]); return (
{ const query = selectedQueryRef.current; if (query) { const existingQuery = reduxQueries[query.id]; if (!existingQuery?.sql && query.sql) { dispatch(startQuery({ ...query, sql: query.sql }, false)); } openAsyncResults(query, displayLimit); } }} onExit={() => { const query = selectedQueryRef.current; if (query) { dispatch(clearQueryResults(query)); selectedQueryRef.current = null; setSelectedQuery(null); } }} modalBody={ selectedQuery ? ( {(() => { const height = reduxQueries[selectedQuery.id]?.state === QueryState.Success && reduxQueries[selectedQuery.id]?.results ? Math.floor(window.innerHeight * 0.5) : undefined; return ( ); })()} ) : null } responsive />
); }; export default QueryTable;