diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 24d0b2e22..dabf0d86a 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -42518,11 +42518,6 @@ "has": "^1.0.1" } }, - "reactable-arc": { - "version": "0.14.42", - "resolved": "https://registry.npmjs.org/reactable-arc/-/reactable-arc-0.14.42.tgz", - "integrity": "sha512-dYWTX+JdknG/DgQlqbjf4ZIJj2z6blEfl7gqTNsLD66gcM+YrgRsTbl4S0E71/iPzfgGujxyk3TLilbozx9c9Q==" - }, "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 0a5481f26..fc24024d8 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -157,7 +157,6 @@ "react-virtualized-auto-sizer": "^1.0.2", "react-virtualized-select": "^3.1.3", "react-window": "^1.8.5", - "reactable-arc": "0.14.42", "redux": "^3.5.2", "redux-localstorage": "^0.4.1", "redux-thunk": "^2.1.0", diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx index 78f2b2f30..04575d558 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { Table } from 'reactable-arc'; import { Alert } from 'react-bootstrap'; import { t } from '@superset-ui/core'; +import TableView from 'src/components/TableView'; import Button from 'src/components/Button'; import Loading from '../../components/Loading'; import ModalTrigger from '../../components/ModalTrigger'; +import { EmptyWrapperType } from '../../components/TableView/TableView'; const propTypes = { dbId: PropTypes.number.isRequired, @@ -42,65 +43,68 @@ const defaultProps = { disabled: false, }; -class EstimateQueryCostButton extends React.PureComponent { - constructor(props) { - super(props); - this.queryCostModal = React.createRef(); - this.onClick = this.onClick.bind(this); - this.renderModalBody = this.renderModalBody.bind(this); - } +const EstimateQueryCostButton = props => { + const { cost } = props.queryCostEstimate; + const tableData = useMemo(() => (Array.isArray(cost) ? cost : []), [cost]); + const columns = useMemo( + () => + Array.isArray(cost) && cost.length + ? Object.keys(cost[0]).map(key => ({ accessor: key, Header: key })) + : [], + [cost], + ); - onClick() { - this.props.getEstimate(); - } + const onClick = () => { + props.getEstimate(); + }; - renderModalBody() { - if (this.props.queryCostEstimate.error !== null) { + const renderModalBody = () => { + if (props.queryCostEstimate.error !== null) { return ( - {this.props.queryCostEstimate.error} + {props.queryCostEstimate.error} ); } - if (this.props.queryCostEstimate.completed) { + if (props.queryCostEstimate.completed) { return ( - ); } return ; - } + }; - render() { - const { disabled, selectedText, tooltip } = this.props; - const btnText = selectedText - ? t('Estimate Selected Query Cost') - : t('Estimate Query Cost'); - return ( - - - {btnText} - - } - /> - - ); - } -} + const { disabled, selectedText, tooltip } = props; + const btnText = selectedText + ? t('Estimate Selected Query Cost') + : t('Estimate Query Cost'); + return ( + + + {btnText} + + } + /> + + ); +}; EstimateQueryCostButton.propTypes = propTypes; EstimateQueryCostButton.defaultProps = defaultProps; diff --git a/superset-frontend/src/components/TableView/index.ts b/superset-frontend/src/components/TableView/index.ts index 88d57c481..c1746ed77 100644 --- a/superset-frontend/src/components/TableView/index.ts +++ b/superset-frontend/src/components/TableView/index.ts @@ -16,4 +16,5 @@ * specific language governing permissions and limitations * under the License. */ +export * from './TableView'; export { default } from './TableView'; diff --git a/superset-frontend/src/components/dataViewCommon/TableCollection.tsx b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx index eb828c262..fae835fb0 100644 --- a/superset-frontend/src/components/dataViewCommon/TableCollection.tsx +++ b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx @@ -52,6 +52,8 @@ export const Table = styled.table` position: sticky; top: 0; + white-space: nowrap; + &:first-of-type { padding-left: ${({ theme }) => theme.gridUnit * 4}px; } diff --git a/superset-frontend/src/explore/components/DisplayQueryButton.jsx b/superset-frontend/src/explore/components/DisplayQueryButton.jsx index 0b4d993cc..285d8ac4e 100644 --- a/superset-frontend/src/explore/components/DisplayQueryButton.jsx +++ b/superset-frontend/src/explore/components/DisplayQueryButton.jsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useState, useMemo } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import PropTypes from 'prop-types'; @@ -27,10 +27,10 @@ import sqlSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'; import jsonSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/json'; import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import { DropdownButton, Row, Col, FormControl } from 'react-bootstrap'; -import { Table } from 'reactable-arc'; -import { t } from '@superset-ui/core'; +import { styled, t } from '@superset-ui/core'; import { Menu } from 'src/common/components'; +import TableView, { EmptyWrapperType } from 'src/components/TableView'; import Button from 'src/components/Button'; import getClientErrorObject from '../../utils/getClientErrorObject'; import CopyToClipboard from '../../components/CopyToClipboard'; @@ -66,77 +66,93 @@ const MENU_KEYS = { DOWNLOAD_AS_IMAGE: 'download_as_image', }; -export class DisplayQueryButton extends React.PureComponent { - constructor(props) { - super(props); - const { datasource } = props.latestQueryFormData; - this.state = { - language: null, - query: null, - data: null, - isLoading: false, - error: null, - filterText: '', - sqlSupported: datasource && datasource.split('__')[1] === 'table', - isPropertiesModalOpen: false, - }; - this.beforeOpen = this.beforeOpen.bind(this); - this.changeFilterText = this.changeFilterText.bind(this); - this.closePropertiesModal = this.closePropertiesModal.bind(this); - this.handleMenuClick = this.handleMenuClick.bind(this); - } +const CopyButton = styled(Button)` + padding: ${({ theme }) => theme.gridUnit / 2}px + ${({ theme }) => theme.gridUnit * 2.5}px; + font-size: ${({ theme }) => theme.typography.sizes.s}px; - beforeOpen(resultType) { - this.setState({ isLoading: true }); + // needed to override button's first-of-type margin: 0 + && { + margin-left: ${({ theme }) => theme.gridUnit * 2}px; + } +`; + +export const DisplayQueryButton = props => { + const { datasource } = props.latestQueryFormData; + + const [language, setLanguage] = useState(null); + const [query, setQuery] = useState(null); + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [filterText, setFilterText] = useState(''); + const [sqlSupported] = useState( + datasource && datasource.split('__')[1] === 'table', + ); + const [isPropertiesModalOpen, setIsPropertiesModalOpen] = useState(false); + + const tableData = useMemo(() => { + if (!data?.length) { + return []; + } + const formattedData = applyFormattingToTabularData(data); + return formattedData.filter(row => + Object.values(row).some(value => + value.toString().toLowerCase().includes(filterText.toLowerCase()), + ), + ); + }, [data, filterText]); + + const columns = useMemo( + () => + data?.length + ? Object.keys(data[0]).map(key => ({ accessor: key, Header: key })) + : [], + [data], + ); + + const beforeOpen = resultType => { + setIsLoading(true); getChartDataRequest({ - formData: this.props.latestQueryFormData, + formData: props.latestQueryFormData, resultFormat: 'json', resultType, }) .then(response => { // Currently displaying of only first query is supported const result = response.result[0]; - this.setState({ - language: result.language, - query: result.query, - data: result.data, - isLoading: false, - error: null, - }); + setLanguage(result.language); + setQuery(result.query); + setData(result.data); + setIsLoading(false); + setError(null); }) .catch(response => { getClientErrorObject(response).then(({ error, statusText }) => { - this.setState({ - error: error || statusText || t('Sorry, An error occurred'), - isLoading: false, - }); + setError(error || statusText || t('Sorry, An error occurred')); + setIsLoading(false); }); }); - } + }; - changeFilterText(event) { - this.setState({ filterText: event.target.value }); - } + const changeFilterText = event => { + setFilterText(event.target.value); + }; - openPropertiesModal() { - this.setState({ isPropertiesModalOpen: true }); - } + const openPropertiesModal = () => { + setIsPropertiesModalOpen(true); + }; - closePropertiesModal() { - this.setState({ isPropertiesModalOpen: false }); - } + const closePropertiesModal = () => { + setIsPropertiesModalOpen(false); + }; - handleMenuClick({ key, domEvent }) { - const { - chartHeight, - slice, - onOpenInEditor, - latestQueryFormData, - } = this.props; + const handleMenuClick = ({ key, domEvent }) => { + const { chartHeight, slice, onOpenInEditor, latestQueryFormData } = props; switch (key) { case MENU_KEYS.EDIT_PROPERTIES: - this.openPropertiesModal(); + openPropertiesModal(); break; case MENU_KEYS.RUN_IN_SQL_LAB: onOpenInEditor(latestQueryFormData); @@ -154,20 +170,20 @@ export class DisplayQueryButton extends React.PureComponent { default: break; } - } + }; - renderQueryModalBody() { - if (this.state.isLoading) { + const renderQueryModalBody = () => { + if (isLoading) { return ; } - if (this.state.error) { - return
{this.state.error}
; + if (error) { + return
{error}
; } - if (this.state.query) { + if (query) { return (
@@ -175,32 +191,16 @@ export class DisplayQueryButton extends React.PureComponent { } /> - - {this.state.query} + + {query}
); } return null; - } + }; - renderResultsModalBody() { - if (this.state.isLoading) { - return ; - } - if (this.state.error) { - return
{this.state.error}
; - } - if (this.state.data) { - if (this.state.data.length === 0) { - return 'No data'; - } - return this.renderDataTable(this.state.data); - } - return null; - } - - renderDataTable(data) { + const renderDataTable = () => { return (
@@ -213,9 +213,9 @@ export class DisplayQueryButton extends React.PureComponent { text={prepareCopyToClipboardTabularData(data)} wrapped={false} copyNode={ - + } /> @@ -223,108 +223,121 @@ export class DisplayQueryButton extends React.PureComponent { -
); - } + }; - renderSamplesModalBody() { - if (this.state.isLoading) { + const renderResultsModalBody = () => { + if (isLoading) { return ; } - if (this.state.error) { - return
{this.state.error}
; + if (error) { + return
{error}
; } - if (this.state.data) { - return this.renderDataTable(this.state.data); + if (data) { + if (data.length === 0) { + return 'No data'; + } + return renderDataTable(); } return null; - } + }; - render() { - const { slice } = this.props; - return ( - - -   - - } - bsSize="sm" - pullRight - id="query" - > - - {slice && [ - - {t('Edit properties')} - , - , - ]} - - {t('View query')} - } - modalTitle={t('View query')} - beforeOpen={() => this.beforeOpen('query')} - modalBody={this.renderQueryModalBody()} - responsive - /> + const renderSamplesModalBody = () => { + if (isLoading) { + return ; + } + if (error) { + return
{error}
; + } + if (data) { + return renderDataTable(); + } + return null; + }; + + const { slice } = props; + return ( + + +   + + } + bsSize="sm" + pullRight + id="query" + > + + {slice && [ + + {t('Edit properties')} + , + , + ]} + + {t('View query')} + } + modalTitle={t('View query')} + beforeOpen={() => beforeOpen('query')} + modalBody={renderQueryModalBody()} + responsive + /> + + + {t('View results')}} + modalTitle={t('View results')} + beforeOpen={() => beforeOpen('results')} + modalBody={renderResultsModalBody()} + responsive + /> + + + {t('View samples')}} + modalTitle={t('View samples')} + beforeOpen={() => beforeOpen('samples')} + modalBody={renderSamplesModalBody()} + responsive + /> + + {sqlSupported && ( + + {t('Run in SQL Lab')} - - {t('View results')}} - modalTitle={t('View results')} - beforeOpen={() => this.beforeOpen('results')} - modalBody={this.renderResultsModalBody()} - responsive - /> - - - {t('View samples')}} - modalTitle={t('View samples')} - beforeOpen={() => this.beforeOpen('samples')} - modalBody={this.renderSamplesModalBody()} - responsive - /> - - {this.state.sqlSupported && ( - - {t('Run in SQL Lab')} - - )} - - {t('Download as image')} - - - - ); - } -} + )} + + {t('Download as image')} + +
+
+ ); +}; DisplayQueryButton.propTypes = propTypes;