diff --git a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts index 271f86e9ed3b8..97dfd2945aaf5 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts @@ -121,14 +121,12 @@ describe('Test datatable', () => { cy.visitChartByName('Daily Totals'); }); it('Data Pane opens and loads results', () => { - cy.get('[data-test="data-tab"]').click(); + cy.contains('Results').click(); cy.get('[data-test="row-count-label"]').contains('26 rows retrieved'); - cy.contains('View results'); cy.get('.ant-empty-description').should('not.exist'); }); it('Datapane loads view samples', () => { - cy.get('[data-test="data-tab"]').click(); - cy.contains('View samples').click(); + cy.contains('Samples').click(); cy.get('[data-test="row-count-label"]').contains('1k rows retrieved'); cy.get('.ant-empty-description').should('not.exist'); }); diff --git a/superset-frontend/src/explore/components/DataTableControl/index.tsx b/superset-frontend/src/explore/components/DataTableControl/index.tsx index 2ae35211822b4..7a25c374bd8ac 100644 --- a/superset-frontend/src/explore/components/DataTableControl/index.tsx +++ b/superset-frontend/src/explore/components/DataTableControl/index.tsx @@ -67,41 +67,56 @@ export const CopyButton = styled(Button)` } `; -const CopyNode = ( - - - -); - export const CopyToClipboardButton = ({ data, columns, }: { data?: Record; columns?: string[]; -}) => ( - -); +}) => { + const theme = useTheme(); + return ( + * { + line-height: 0; + } + `} + /> + } + /> + ); +}; export const FilterInput = ({ onChangeHandler, }: { onChangeHandler(filterText: string): void; }) => { + const theme = useTheme(); const debouncedChangeHandler = debounce(onChangeHandler, SLOW_DEBOUNCE); return ( } placeholder={t('Search')} onChange={(event: any) => { const filterText = event.target.value; debouncedChangeHandler(filterText); }} + css={css` + width: 200px; + margin-right: ${theme.gridUnit * 2}px; + `} /> ); }; diff --git a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.test.tsx b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.test.tsx index 9905d8f5c6d3c..786150449ee20 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.test.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.test.tsx @@ -21,7 +21,11 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import fetchMock from 'fetch-mock'; import * as copyUtils from 'src/utils/copy'; -import { render, screen } from 'spec/helpers/testing-library'; +import { + render, + screen, + waitForElementToBeRemoved, +} from 'spec/helpers/testing-library'; import { DataTablesPane } from '.'; const createProps = () => ({ @@ -50,7 +54,6 @@ const createProps = () => ({ sort_y_axis: 'alpha_asc', extra_form_data: {}, }, - tableSectionHeight: 156.9, chartStatus: 'rendered', onCollapseChange: jest.fn(), queriesResponse: [ @@ -60,91 +63,162 @@ const createProps = () => ({ ], }); -test('Rendering DataTablesPane correctly', () => { - const props = createProps(); - render(, { useRedux: true }); - expect(screen.getByTestId('some-purposeful-instance')).toBeVisible(); - expect(screen.getByRole('tablist')).toBeVisible(); - expect(screen.getByRole('tab', { name: 'right Data' })).toBeVisible(); - expect(screen.getByRole('img', { name: 'right' })).toBeVisible(); -}); +describe('DataTablesPane', () => { + // Collapsed/expanded state depends on local storage + // We need to clear it manually - otherwise initial state would depend on the order of tests + beforeEach(() => { + localStorage.clear(); + }); -test('Should show tabs', async () => { - const props = createProps(); - render(, { useRedux: true }); - expect(screen.queryByText('View results')).not.toBeInTheDocument(); - expect(screen.queryByText('View samples')).not.toBeInTheDocument(); - userEvent.click(await screen.findByText('Data')); - expect(await screen.findByText('View results')).toBeVisible(); - expect(screen.getByText('View samples')).toBeVisible(); -}); + afterAll(() => { + localStorage.clear(); + }); -test('Should show tabs: View results', async () => { - const props = createProps(); - render(, { - useRedux: true, + test('Rendering DataTablesPane correctly', () => { + const props = createProps(); + render(, { useRedux: true }); + expect(screen.getByText('Results')).toBeVisible(); + expect(screen.getByText('Samples')).toBeVisible(); + expect(screen.getByLabelText('Expand data panel')).toBeVisible(); }); - userEvent.click(await screen.findByText('Data')); - userEvent.click(await screen.findByText('View results')); - expect(screen.getByText('0 rows retrieved')).toBeVisible(); -}); -test('Should show tabs: View samples', async () => { - const props = createProps(); - render(, { - useRedux: true, + test('Collapse/Expand buttons', async () => { + const props = createProps(); + render(, { + useRedux: true, + }); + expect( + screen.queryByLabelText('Collapse data panel'), + ).not.toBeInTheDocument(); + userEvent.click(screen.getByLabelText('Expand data panel')); + expect(await screen.findByLabelText('Collapse data panel')).toBeVisible(); + expect( + screen.queryByLabelText('Expand data panel'), + ).not.toBeInTheDocument(); }); - userEvent.click(await screen.findByText('Data')); - expect(screen.queryByText('0 rows retrieved')).not.toBeInTheDocument(); - userEvent.click(await screen.findByText('View samples')); - expect(await screen.findByText('0 rows retrieved')).toBeVisible(); -}); -test('Should copy data table content correctly', async () => { - fetchMock.post( - 'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D', - { - result: [ - { - data: [{ __timestamp: 1230768000000, genre: 'Action' }], - colnames: ['__timestamp', 'genre'], - coltypes: [2, 1], + test('Should show tabs: View results', async () => { + const props = createProps(); + render(, { + useRedux: true, + }); + userEvent.click(screen.getByText('Results')); + expect(await screen.findByText('0 rows retrieved')).toBeVisible(); + expect(await screen.findByLabelText('Collapse data panel')).toBeVisible(); + localStorage.clear(); + }); + + test('Should show tabs: View samples', async () => { + const props = createProps(); + render(, { + useRedux: true, + }); + userEvent.click(screen.getByText('Samples')); + expect(await screen.findByText('0 rows retrieved')).toBeVisible(); + expect(await screen.findByLabelText('Collapse data panel')).toBeVisible(); + }); + + test('Should copy data table content correctly', async () => { + fetchMock.post( + 'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D', + { + result: [ + { + data: [{ __timestamp: 1230768000000, genre: 'Action' }], + colnames: ['__timestamp', 'genre'], + coltypes: [2, 1], + }, + ], + }, + ); + const copyToClipboardSpy = jest.spyOn(copyUtils, 'default'); + const props = createProps(); + render( + , + { + useRedux: true, + initialState: { + explore: { + timeFormattedColumns: { + '34__table': ['__timestamp'], + }, + }, }, - ], - }, - ); - const copyToClipboardSpy = jest.spyOn(copyUtils, 'default'); - const props = createProps(); - render( - { + fetchMock.post( + 'glob:*/api/v1/chart/data?form_data=%7B%22slice_id%22%3A456%7D', + { + result: [ { + data: [ + { __timestamp: 1230768000000, genre: 'Action' }, + { __timestamp: 1230768000010, genre: 'Horror' }, + ], colnames: ['__timestamp', 'genre'], coltypes: [2, 1], }, ], - }} - />, - { - useRedux: true, - initialState: { - explore: { - timeFormattedColumns: { - '34__table': ['__timestamp'], + }, + ); + const props = createProps(); + render( + , + { + useRedux: true, + initialState: { + explore: { + timeFormattedColumns: { + '34__table': ['__timestamp'], + }, }, }, }, - }, - ); - userEvent.click(await screen.findByText('Data')); - expect(await screen.findByText('1 rows retrieved')).toBeVisible(); + ); + userEvent.click(screen.getByText('Results')); + expect(await screen.findByText('2 rows retrieved')).toBeVisible(); + expect(screen.getByText('Action')).toBeVisible(); + expect(screen.getByText('Horror')).toBeVisible(); - userEvent.click(screen.getByRole('button', { name: 'Copy' })); - expect(copyToClipboardSpy).toHaveBeenCalledWith( - '2009-01-01 00:00:00\tAction\n', - ); - fetchMock.done(); + userEvent.type(screen.getByPlaceholderText('Search'), 'hor'); + + await waitForElementToBeRemoved(() => screen.queryByText('Action')); + expect(screen.getByText('Horror')).toBeVisible(); + expect(screen.queryByText('Action')).not.toBeInTheDocument(); + fetchMock.restore(); + }); }); diff --git a/superset-frontend/src/explore/components/DataTablesPane/index.tsx b/superset-frontend/src/explore/components/DataTablesPane/index.tsx index 5d935caa63ddd..a41af3626f1e4 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/index.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/index.tsx @@ -16,15 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useState, + MouseEvent, +} from 'react'; import { + css, ensureIsArray, GenericDataType, JsonObject, styled, t, + useTheme, } from '@superset-ui/core'; -import Collapse from 'src/components/Collapse'; +import Icons from 'src/components/Icons'; import Tabs from 'src/components/Tabs'; import Loading from 'src/components/Loading'; import { EmptyStateMedium } from 'src/components/EmptyState'; @@ -58,53 +66,58 @@ const getDefaultDataTablesState = (value: any) => ({ const DATA_TABLE_PAGE_SIZE = 50; -const DATAPANEL_KEY = 'data'; - const TableControlsWrapper = styled.div` - display: flex; - align-items: center; - - span { - flex-shrink: 0; - } + ${({ theme }) => ` + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: ${theme.gridUnit * 2}px; + + span { + flex-shrink: 0; + } + `} `; const SouthPane = styled.div` - position: relative; - background-color: ${({ theme }) => theme.colors.grayscale.light5}; - z-index: 5; - overflow: hidden; -`; - -const TabsWrapper = styled.div<{ contentHeight: number }>` - height: ${({ contentHeight }) => contentHeight}px; - overflow: hidden; + ${({ theme }) => ` + position: relative; + background-color: ${theme.colors.grayscale.light5}; + z-index: 5; + overflow: hidden; - .table-condensed { - height: 100%; - overflow: auto; - } -`; + .ant-tabs { + height: 100%; + } -const CollapseWrapper = styled.div` - height: 100%; + .ant-tabs-content-holder { + height: 100%; + } - .collapse-inner { - height: 100%; + .ant-tabs-content { + height: 100%; + } - .ant-collapse-item { + .ant-tabs-tabpane { + display: flex; + flex-direction: column; height: 100%; - .ant-collapse-content { - height: calc(100% - ${({ theme }) => theme.gridUnit * 8}px); + .table-condensed { + height: 100%; + overflow: auto; + margin-bottom: ${theme.gridUnit * 4}px; - .ant-collapse-content-box { - padding-top: 0; - height: 100%; + .table { + margin-bottom: ${theme.gridUnit * 2}px; } } + + .pagination-container > ul[role='navigation'] { + margin-top: 0; + } } - } + `} `; const Error = styled.pre` @@ -117,7 +130,6 @@ interface DataTableProps { datasource: string | undefined; filterText: string; data: object[] | undefined; - timeFormattedColumns: string[] | undefined; isLoading: boolean; error: string | undefined; errorMessage: React.ReactElement | undefined; @@ -130,12 +142,12 @@ const DataTable = ({ datasource, filterText, data, - timeFormattedColumns, isLoading, error, errorMessage, type, }: DataTableProps) => { + const timeFormattedColumns = useTimeFormattedColumns(datasource); // this is to preserve the order of the columns, even if there are integer values, // while also only grabbing the first column's keys const columns = useTableColumns( @@ -185,9 +197,42 @@ const DataTable = ({ return null; }; +const TableControls = ({ + data, + datasourceId, + onInputChange, + columnNames, + isLoading, +}: { + data: Record[]; + datasourceId?: string; + onInputChange: (input: string) => void; + columnNames: string[]; + isLoading: boolean; +}) => { + const timeFormattedColumns = useTimeFormattedColumns(datasourceId); + const formattedData = useMemo( + () => applyFormattingToTabularData(data, timeFormattedColumns), + [data, timeFormattedColumns], + ); + return ( + + +
+ + +
+
+ ); +}; + export const DataTablesPane = ({ queryFormData, - tableSectionHeight, onCollapseChange, chartStatus, ownState, @@ -195,19 +240,19 @@ export const DataTablesPane = ({ queriesResponse, }: { queryFormData: Record; - tableSectionHeight: number; chartStatus: string; ownState?: JsonObject; - onCollapseChange: (openPanelName: string) => void; + onCollapseChange: (isOpen: boolean) => void; errorMessage?: JSX.Element; queriesResponse: Record; }) => { + const theme = useTheme(); const [data, setData] = useState(getDefaultDataTablesState(undefined)); const [isLoading, setIsLoading] = useState(getDefaultDataTablesState(true)); const [columnNames, setColumnNames] = useState(getDefaultDataTablesState([])); const [columnTypes, setColumnTypes] = useState(getDefaultDataTablesState([])); const [error, setError] = useState(getDefaultDataTablesState('')); - const [filterText, setFilterText] = useState(''); + const [filterText, setFilterText] = useState(getDefaultDataTablesState('')); const [activeTabKey, setActiveTabKey] = useState( RESULT_TYPES.results, ); @@ -218,24 +263,6 @@ export const DataTablesPane = ({ getItem(LocalStorageKeys.is_datapanel_open, false), ); - const timeFormattedColumns = useTimeFormattedColumns( - queryFormData?.datasource, - ); - - const formattedData = useMemo( - () => ({ - [RESULT_TYPES.results]: applyFormattingToTabularData( - data[RESULT_TYPES.results], - timeFormattedColumns, - ), - [RESULT_TYPES.samples]: applyFormattingToTabularData( - data[RESULT_TYPES.samples], - timeFormattedColumns, - ), - }), - [data, timeFormattedColumns], - ); - const getData = useCallback( (resultType: 'samples' | 'results') => { setIsLoading(prevIsLoading => ({ @@ -381,81 +408,121 @@ export const DataTablesPane = ({ errorMessage, ]); - const TableControls = ( - - - - - + const handleCollapseChange = useCallback( + (isOpen: boolean) => { + onCollapseChange(isOpen); + setPanelOpen(isOpen); + }, + [onCollapseChange], ); - const handleCollapseChange = (openPanelName: string) => { - onCollapseChange(openPanelName); - setPanelOpen(!!openPanelName); - }; + const handleTabClick = useCallback( + (tabKey: string, e: MouseEvent) => { + if (!panelOpen) { + handleCollapseChange(true); + } else if (tabKey === activeTabKey) { + e.preventDefault(); + handleCollapseChange(false); + } + setActiveTabKey(tabKey); + }, + [activeTabKey, handleCollapseChange, panelOpen], + ); + + const CollapseButton = useMemo(() => { + const caretIcon = panelOpen ? ( + + ) : ( + + ); + return ( + + {panelOpen ? ( + handleCollapseChange(false)} + > + {caretIcon} + + ) : ( + handleCollapseChange(true)} + > + {caretIcon} + + )} + + ); + }, [handleCollapseChange, panelOpen, theme.colors.grayscale.base]); return ( - - - - - - - - - - - - - - - - + + + + setFilterText(prevState => ({ + ...prevState, + [RESULT_TYPES.results]: input, + })) + } + isLoading={isLoading[RESULT_TYPES.results]} + /> + + + + + setFilterText(prevState => ({ + ...prevState, + [RESULT_TYPES.samples]: input, + })) + } + isLoading={isLoading[RESULT_TYPES.samples]} + /> + + + ); }; diff --git a/superset-frontend/src/explore/components/ExploreChartPanel.jsx b/superset-frontend/src/explore/components/ExploreChartPanel.jsx index 8fb1c3ef073d9..37523713a8e71 100644 --- a/superset-frontend/src/explore/components/ExploreChartPanel.jsx +++ b/superset-frontend/src/explore/components/ExploreChartPanel.jsx @@ -19,7 +19,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import Split from 'react-split'; -import { styled, SupersetClient, useTheme } from '@superset-ui/core'; +import { css, styled, SupersetClient, useTheme } from '@superset-ui/core'; import { useResizeDetector } from 'react-resize-detector'; import { chartPropShape } from 'src/dashboard/util/propShapes'; import ChartContainer from 'src/components/Chart/ChartContainer'; @@ -41,8 +41,6 @@ const propTypes = { dashboardId: PropTypes.number, column_formats: PropTypes.object, containerId: PropTypes.string.isRequired, - height: PropTypes.string.isRequired, - width: PropTypes.string.isRequired, isStarred: PropTypes.bool.isRequired, slice: PropTypes.object, sliceName: PropTypes.string, @@ -61,11 +59,8 @@ const propTypes = { const GUTTER_SIZE_FACTOR = 1.25; -const CHART_PANEL_PADDING_HORIZ = 30; -const CHART_PANEL_PADDING_VERTICAL = 15; - -const INITIAL_SIZES = [90, 10]; -const MIN_SIZES = [300, 50]; +const INITIAL_SIZES = [100, 0]; +const MIN_SIZES = [300, 65]; const DEFAULT_SOUTH_PANE_HEIGHT_PERCENT = 40; const Styles = styled.div` @@ -109,28 +104,42 @@ const Styles = styled.div` } `; -const ExploreChartPanel = props => { +const ExploreChartPanel = ({ + chart, + slice, + vizType, + ownState, + triggerRender, + force, + datasource, + errorMessage, + form_data: formData, + onQuery, + refreshOverlayVisible, + actions, + timeout, + standalone, +}) => { const theme = useTheme(); const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR; const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR; - const { width: chartPanelWidth, ref: chartPanelRef } = useResizeDetector({ + const { + width: chartPanelWidth, + height: chartPanelHeight, + ref: chartPanelRef, + } = useResizeDetector({ refreshMode: 'debounce', refreshRate: 300, }); - const { height: pillsHeight, ref: pillsRef } = useResizeDetector({ - refreshMode: 'debounce', - refreshRate: 1000, - }); const [splitSizes, setSplitSizes] = useState( getItem(LocalStorageKeys.chart_split_sizes, INITIAL_SIZES), ); - const { slice } = props; const updateQueryContext = useCallback( async function fetchChartData() { if (slice && slice.query_context === null) { const queryContext = buildV1ChartDataPayload({ formData: slice.form_data, - force: props.force, + force, resultFormat: 'json', resultType: 'full', setDataMask: null, @@ -154,34 +163,6 @@ const ExploreChartPanel = props => { updateQueryContext(); }, [updateQueryContext]); - const calcSectionHeight = useCallback( - percent => { - let containerHeight = parseInt(props.height, 10); - if (pillsHeight) { - containerHeight -= pillsHeight; - } - return ( - (containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin) - ); - }, - [gutterHeight, gutterMargin, pillsHeight, props.height, props.standalone], - ); - - const [tableSectionHeight, setTableSectionHeight] = useState( - calcSectionHeight(INITIAL_SIZES[1]), - ); - - const recalcPanelSizes = useCallback( - ([, southPercent]) => { - setTableSectionHeight(calcSectionHeight(southPercent)); - }, - [calcSectionHeight], - ); - - useEffect(() => { - recalcPanelSizes(splitSizes); - }, [recalcPanelSizes, splitSizes]); - useEffect(() => { setItem(LocalStorageKeys.chart_split_sizes, splitSizes); }, [splitSizes]); @@ -191,19 +172,19 @@ const ExploreChartPanel = props => { }; const refreshCachedQuery = () => { - props.actions.postChartFormData( - props.form_data, + actions.postChartFormData( + formData, true, - props.timeout, - props.chart.id, + timeout, + chart.id, undefined, - props.ownState, + ownState, ); }; - const onCollapseChange = openPanelName => { + const onCollapseChange = useCallback(isOpen => { let splitSizes; - if (!openPanelName) { + if (!isOpen) { splitSizes = INITIAL_SIZES; } else { splitSizes = [ @@ -212,53 +193,84 @@ const ExploreChartPanel = props => { ]; } setSplitSizes(splitSizes); - }; - const renderChart = useCallback(() => { - const { chart, vizType } = props; - const newHeight = - vizType === 'filter_box' - ? calcSectionHeight(100) - CHART_PANEL_PADDING_VERTICAL - : calcSectionHeight(splitSizes[0]) - CHART_PANEL_PADDING_VERTICAL; - const chartWidth = chartPanelWidth - CHART_PANEL_PADDING_HORIZ; - return ( - chartWidth > 0 && ( - - ) - ); - }, [calcSectionHeight, chartPanelWidth, props, splitSizes]); + }, []); + + const renderChart = useCallback( + () => ( +
+ {chartPanelWidth && chartPanelHeight && ( + + )} +
+ ), + [ + actions.setControlValue, + chart.annotationData, + chart.chartAlert, + chart.chartStackTrace, + chart.chartStatus, + chart.id, + chart.queriesResponse, + chart.triggerQuery, + chartPanelHeight, + chartPanelRef, + chartPanelWidth, + datasource, + errorMessage, + force, + formData, + onQuery, + ownState, + refreshOverlayVisible, + timeout, + triggerRender, + vizType, + ], + ); const panelBody = useMemo( () => ( -
+
{renderChart()}
@@ -266,14 +278,9 @@ const ExploreChartPanel = props => { [chartPanelRef, renderChart], ); - const standaloneChartBody = useMemo( - () =>
{renderChart()}
, - [chartPanelRef, renderChart], - ); + const standaloneChartBody = useMemo(() => renderChart(), [renderChart]); - const [queryFormData, setQueryFormData] = useState( - props.chart.latestQueryFormData, - ); + const [queryFormData, setQueryFormData] = useState(chart.latestQueryFormData); useEffect(() => { // only update when `latestQueryFormData` changes AND `triggerRender` @@ -281,13 +288,13 @@ const ExploreChartPanel = props => { // as this can trigger a query downstream based on incomplete form data. // (`latestQueryFormData` is only updated when a a valid request has been // triggered). - if (!props.triggerRender) { - setQueryFormData(props.chart.latestQueryFormData); + if (!triggerRender) { + setQueryFormData(chart.latestQueryFormData); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.chart.latestQueryFormData]); + }, [chart.latestQueryFormData]); - if (props.standalone) { + if (standalone) { // dom manipulation hack to get rid of the boostrap theme's body background const standaloneClass = 'background-transparent'; const bodyClasses = document.body.className.split(' '); @@ -302,8 +309,8 @@ const ExploreChartPanel = props => { }); return ( - - {props.vizType === 'filter_box' ? ( + + {vizType === 'filter_box' ? ( panelBody ) : ( { gutterSize={gutterHeight} onDragEnd={onDragEnd} elementStyle={elementStyle} + expandToMin > {panelBody} )} diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx index b856ba706cf88..7299adf251085 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx @@ -63,8 +63,6 @@ import ConnectedExploreChartHeader from '../ExploreChartHeader'; const propTypes = { ...ExploreChartPanel.propTypes, - height: PropTypes.string, - width: PropTypes.string, actions: PropTypes.object.isRequired, datasource_type: PropTypes.string.isRequired, dashboardId: PropTypes.number, @@ -135,6 +133,7 @@ const ExplorePanelContainer = styled.div` flex: 1; min-width: ${theme.gridUnit * 128}px; border-left: 1px solid ${theme.colors.grayscale.light2}; + padding: 0 ${theme.gridUnit * 4}px; .panel { margin-bottom: 0; } @@ -172,23 +171,6 @@ const ExplorePanelContainer = styled.div` `}; `; -const getWindowSize = () => ({ - height: window.innerHeight, - width: window.innerWidth, -}); - -function useWindowSize({ delayMs = 250 } = {}) { - const [size, setSize] = useState(getWindowSize()); - - useEffect(() => { - const onWindowResize = debounce(() => setSize(getWindowSize()), delayMs); - window.addEventListener('resize', onWindowResize); - return () => window.removeEventListener('resize', onWindowResize); - }, []); - - return size; -} - const updateHistory = debounce( async (formData, datasetId, isReplace, standalone, force, title, tabId) => { const payload = { ...formData }; @@ -246,7 +228,6 @@ function ExploreViewContainer(props) { const [lastQueriedControls, setLastQueriedControls] = useState( props.controls, ); - const windowSize = useWindowSize(); const [showingModal, setShowingModal] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false); @@ -254,11 +235,6 @@ function ExploreViewContainer(props) { const tabId = useTabId(); const theme = useTheme(); - const width = `${windowSize.width}px`; - const navHeight = props.standalone ? 0 : 120; - const height = props.forcedHeight - ? `${props.forcedHeight}px` - : `${windowSize.height - navHeight}px`; const defaultSidebarsWidth = { controls_width: 320, @@ -515,8 +491,6 @@ function ExploreViewContainer(props) { function renderChartContainer() { return (