/** * 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 React, { useState, useEffect, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import Split from 'react-split'; import { css, ensureIsArray, styled, SupersetClient, t, useTheme, getChartMetadataRegistry, DatasourceType, } from '@superset-ui/core'; import { useResizeDetector } from 'react-resize-detector'; import { chartPropShape } from 'src/dashboard/util/propShapes'; import ChartContainer from 'src/components/Chart/ChartContainer'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { getItem, setItem, LocalStorageKeys, } from 'src/utils/localStorageHelpers'; import Alert from 'src/components/Alert'; import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal'; import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils'; import { DataTablesPane } from './DataTablesPane'; import { buildV1ChartDataPayload } from '../exploreUtils'; import { ChartPills } from './ChartPills'; import { ExploreAlert } from './ExploreAlert'; import { getChartRequiredFieldsMissingMessage } from '../../utils/getChartRequiredFieldsMissingMessage'; const propTypes = { actions: PropTypes.object.isRequired, onQuery: PropTypes.func, can_overwrite: PropTypes.bool.isRequired, can_download: PropTypes.bool.isRequired, datasource: PropTypes.object, dashboardId: PropTypes.number, column_formats: PropTypes.object, containerId: PropTypes.string.isRequired, isStarred: PropTypes.bool.isRequired, slice: PropTypes.object, sliceName: PropTypes.string, table_name: PropTypes.string, vizType: PropTypes.string.isRequired, form_data: PropTypes.object, ownState: PropTypes.object, standalone: PropTypes.bool, force: PropTypes.bool, timeout: PropTypes.number, chartIsStale: PropTypes.bool, chart: chartPropShape, errorMessage: PropTypes.node, triggerRender: PropTypes.bool, }; const GUTTER_SIZE_FACTOR = 1.25; const INITIAL_SIZES = [100, 0]; const MIN_SIZES = [300, 65]; const DEFAULT_SOUTH_PANE_HEIGHT_PERCENT = 40; const Styles = styled.div` display: flex; flex-direction: column; align-items: stretch; align-content: stretch; overflow: auto; box-shadow: none; height: 100%; & > div { height: 100%; } .gutter { border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; width: ${({ theme }) => theme.gridUnit * 9}px; margin: ${({ theme }) => theme.gridUnit * GUTTER_SIZE_FACTOR}px auto; } .gutter.gutter-vertical { display: ${({ showSplite }) => (showSplite ? 'block' : 'none')}; cursor: row-resize; } .ant-collapse { .ant-tabs { height: 100%; .ant-tabs-nav { padding-left: ${({ theme }) => theme.gridUnit * 5}px; margin: 0; } .ant-tabs-content-holder { overflow: hidden; .ant-tabs-content { height: 100%; } } } } `; const ExploreChartPanel = ({ chart, slice, vizType, ownState, triggerRender, force, datasource, errorMessage, form_data: formData, onQuery, actions, timeout, standalone, chartIsStale, chartAlert, }) => { const theme = useTheme(); const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR; const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR; const { width: chartPanelWidth, height: chartPanelHeight, ref: chartPanelRef, } = useResizeDetector({ refreshMode: 'debounce', refreshRate: 300, }); const [splitSizes, setSplitSizes] = useState( isFeatureEnabled(FeatureFlag.DATAPANEL_CLOSED_BY_DEFAULT) ? INITIAL_SIZES : getItem(LocalStorageKeys.chart_split_sizes, INITIAL_SIZES), ); const [showSplite, setShowSplit] = useState( isFeatureEnabled(FeatureFlag.DATAPANEL_CLOSED_BY_DEFAULT) ? false : getItem(LocalStorageKeys.is_datapanel_open, false), ); const [showDatasetModal, setShowDatasetModal] = useState(false); const metaDataRegistry = getChartMetadataRegistry(); const { useLegacyApi } = metaDataRegistry.get(vizType) ?? {}; const vizTypeNeedsDataset = useLegacyApi && datasource.type !== DatasourceType.Table; // added boolean column to below show boolean so that the errors aren't overlapping const showAlertBanner = !chartAlert && chartIsStale && !vizTypeNeedsDataset && chart.chartStatus !== 'failed' && ensureIsArray(chart.queriesResponse).length > 0; const updateQueryContext = useCallback( async function fetchChartData() { if (slice && slice.query_context === null) { const queryContext = buildV1ChartDataPayload({ formData: slice.form_data, force, resultFormat: 'json', resultType: 'full', setDataMask: null, ownState: null, }); await SupersetClient.put({ endpoint: `/api/v1/chart/${slice.slice_id}`, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query_context: JSON.stringify(queryContext), query_context_generation: true, }), }); } }, [slice], ); useEffect(() => { updateQueryContext(); }, [updateQueryContext]); useEffect(() => { setItem(LocalStorageKeys.chart_split_sizes, splitSizes); }, [splitSizes]); const onDragEnd = useCallback(sizes => { setSplitSizes(sizes); }, []); const refreshCachedQuery = useCallback(() => { actions.setForceQuery(true); actions.postChartFormData( formData, true, timeout, chart.id, undefined, ownState, ); actions.updateQueryFormData(formData, chart.id); }, [actions, chart.id, formData, ownState, timeout]); const onCollapseChange = useCallback(isOpen => { let splitSizes; if (!isOpen) { splitSizes = INITIAL_SIZES; } else { splitSizes = [ 100 - DEFAULT_SOUTH_PANE_HEIGHT_PERCENT, DEFAULT_SOUTH_PANE_HEIGHT_PERCENT, ]; } setSplitSizes(splitSizes); setShowSplit(isOpen); }, []); const renderChart = useCallback( () => (