diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx
index 0d2914522d7..35209bb94af 100644
--- a/superset-frontend/src/components/Chart/Chart.jsx
+++ b/superset-frontend/src/components/Chart/Chart.jsx
@@ -18,7 +18,7 @@
*/
import PropTypes from 'prop-types';
import React from 'react';
-import { styled, logging, t } from '@superset-ui/core';
+import { styled, logging, t, ensureIsArray } from '@superset-ui/core';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
@@ -288,6 +288,23 @@ class Chart extends React.PureComponent {
);
}
+ if (
+ !isLoading &&
+ !chartAlert &&
+ isFaded &&
+ ensureIsArray(queriesResponse).length === 0
+ ) {
+ return (
+
+ );
+ }
+
return (
{
+describe('ControlPanelsContainer', () => {
beforeAll(() => {
getChartControlPanelRegistry().registerValue('table', {
controlPanelSections: [
@@ -90,6 +90,10 @@ describe('ControlPanelsContainer2', () => {
form_data: getFormDataFromControls(controls),
isDatasourceMetaLoading: false,
exploreState: {},
+ chart: {
+ queriesResponse: null,
+ chartStatus: 'success',
+ },
} as ControlPanelsContainerProps;
}
diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
index 832d3552f6e..e20e8574e35 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
@@ -18,6 +18,7 @@
*/
/* eslint camelcase: 0 */
import React, {
+ ReactNode,
useCallback,
useContext,
useEffect,
@@ -33,6 +34,7 @@ import {
QueryFormData,
DatasourceType,
css,
+ SupersetTheme,
} from '@superset-ui/core';
import {
ControlPanelSectionConfig,
@@ -54,10 +56,12 @@ import { getSectionsToRender } from 'src/explore/controlUtils';
import { ExploreActions } from 'src/explore/actions/exploreActions';
import { ExplorePageState } from 'src/explore/reducers/getInitialState';
import { ChartState } from 'src/explore/types';
+import { Tooltip } from 'src/components/Tooltip';
import ControlRow from './ControlRow';
import Control from './Control';
import { ControlPanelAlert } from './ControlPanelAlert';
+import { RunQueryButton } from './RunQueryButton';
export type ControlPanelsContainerProps = {
exploreState: ExplorePageState['explore'];
@@ -67,6 +71,11 @@ export type ControlPanelsContainerProps = {
controls: Record;
form_data: QueryFormData;
isDatasourceMetaLoading: boolean;
+ errorMessage: ReactNode;
+ onQuery: () => void;
+ onStop: () => void;
+ canStopQuery: boolean;
+ chartIsStale: boolean;
};
export type ExpandedControlPanelSectionConfig = Omit<
@@ -76,13 +85,33 @@ export type ExpandedControlPanelSectionConfig = Omit<
controlSetRows: ExpandedControlItem[][];
};
+const actionButtonsContainerStyles = (theme: SupersetTheme) => css`
+ display: flex;
+ position: sticky;
+ bottom: 0;
+ flex-direction: column;
+ align-items: center;
+ padding: ${theme.gridUnit * 4}px;
+ background: linear-gradient(
+ transparent,
+ ${theme.colors.grayscale.light5} ${theme.opacity.mediumLight}
+ );
+
+ & > button {
+ min-width: 156px;
+ }
+`;
+
const Styles = styled.div`
+ position: relative;
height: 100%;
width: 100%;
- overflow: auto;
- overflow-x: visible;
+
+ // Resizable add overflow-y: auto as a style to this div
+ // To override it, we need to use !important
+ overflow: visible !important;
#controlSections {
- min-height: 100%;
+ height: 100%;
overflow: visible;
}
.nav-tabs {
@@ -105,15 +134,22 @@ const Styles = styled.div`
`;
const ControlPanelsTabs = styled(Tabs)`
- .ant-tabs-nav-list {
- width: ${({ fullWidth }) => (fullWidth ? '100%' : '50%')};
- }
- .ant-tabs-content-holder {
- overflow: visible;
- }
- .ant-tabs-tabpane {
+ ${({ theme, fullWidth }) => css`
height: 100%;
- }
+ overflow: visible;
+ .ant-tabs-nav {
+ margin-bottom: 0;
+ }
+ .ant-tabs-nav-list {
+ width: ${fullWidth ? '100%' : '50%'};
+ }
+ .ant-tabs-tabpane {
+ height: 100%;
+ }
+ .ant-tabs-content-holder {
+ padding-top: ${theme.gridUnit * 4}px;
+ }
+ `}
`;
const isTimeSection = (section: ControlPanelSectionConfig): boolean =>
@@ -350,7 +386,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
box-shadow: none;
&:last-child {
- padding-bottom: ${theme.gridUnit * 10}px;
+ padding-bottom: ${theme.gridUnit * 16}px;
}
.panel-body {
@@ -432,6 +468,32 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
[handleClearFormClick, handleContinueClick, hasControlsTransferred],
);
+ const dataTabTitle = useMemo(
+ () => (
+ <>
+ {t('Data')}
+ {props.errorMessage && (
+ css`
+ font-size: ${theme.typography.sizes.xs}px;
+ margin-left: ${theme.gridUnit * 2}px;
+ `}
+ >
+ {' '}
+
+
+
+
+ )}
+ >
+ ),
+ [props.errorMessage],
+ );
+
const controlPanelRegistry = getChartControlPanelRegistry();
if (
!controlPanelRegistry.has(props.form_data.viz_type) &&
@@ -448,8 +510,9 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
id="controlSections"
data-test="control-tabs"
fullWidth={showCustomizeTab}
+ allowOverflow={false}
>
-
+
{
)}
+
+
+
);
};
diff --git a/superset-frontend/src/explore/components/ExploreAdditionalActionsMenu/index.jsx b/superset-frontend/src/explore/components/ExploreAdditionalActionsMenu/index.jsx
index 640912d694c..f02ab016225 100644
--- a/superset-frontend/src/explore/components/ExploreAdditionalActionsMenu/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreAdditionalActionsMenu/index.jsx
@@ -86,8 +86,8 @@ const MenuItemWithCheckboxContainer = styled.div`
const MenuTrigger = styled(Button)`
${({ theme }) => css`
- width: ${theme.gridUnit * 6}px;
- height: ${theme.gridUnit * 6}px;
+ width: ${theme.gridUnit * 8}px;
+ height: ${theme.gridUnit * 8}px;
padding: 0;
border: 1px solid ${theme.colors.primary.dark2};
@@ -425,7 +425,7 @@ const ExploreAdditionalActionsMenu = ({
>
diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/ExploreChartHeader.test.tsx b/superset-frontend/src/explore/components/ExploreChartHeader/ExploreChartHeader.test.tsx
index 3c90d4650d6..8f298dce767 100644
--- a/superset-frontend/src/explore/components/ExploreChartHeader/ExploreChartHeader.test.tsx
+++ b/superset-frontend/src/explore/components/ExploreChartHeader/ExploreChartHeader.test.tsx
@@ -90,6 +90,7 @@ const createProps = () => ({
user: {
userId: 1,
},
+ onSaveChart: jest.fn(),
});
test('Cancelling changes to the properties should reset previous properties', () => {
@@ -115,3 +116,17 @@ test('Cancelling changes to the properties should reset previous properties', ()
expect(screen.getByDisplayValue(prevChartName)).toBeInTheDocument();
});
+
+test('Save chart', () => {
+ const props = createProps();
+ render(, { useRedux: true });
+ userEvent.click(screen.getByText('Save'));
+ expect(props.onSaveChart).toHaveBeenCalled();
+});
+
+test('Save disabled', () => {
+ const props = createProps();
+ render(, { useRedux: true });
+ userEvent.click(screen.getByText('Save'));
+ expect(props.onSaveChart).not.toHaveBeenCalled();
+});
diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx
index bbaa34648b1..1be9dd77a2b 100644
--- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx
@@ -24,7 +24,6 @@ import {
CategoricalColorNamespace,
css,
SupersetClient,
- styled,
t,
} from '@superset-ui/core';
import {
@@ -36,9 +35,12 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { chartPropShape } from 'src/dashboard/util/propShapes';
import AlteredSliceTag from 'src/components/AlteredSliceTag';
import FaveStar from 'src/components/FaveStar';
+import Button from 'src/components/Button';
+import Icons from 'src/components/Icons';
import PropertiesModal from 'src/explore/components/PropertiesModal';
import { sliceUpdated } from 'src/explore/actions/exploreActions';
import CertifiedBadge from 'src/components/CertifiedBadge';
+import { Tooltip } from 'src/components/Tooltip';
import ExploreAdditionalActionsMenu from '../ExploreAdditionalActionsMenu';
import { ChartEditableTitle } from './ChartEditableTitle';
@@ -55,60 +57,58 @@ const propTypes = {
ownState: PropTypes.object,
timeout: PropTypes.number,
chart: chartPropShape,
+ saveDisabled: PropTypes.bool,
};
-const StyledHeader = styled.div`
- ${({ theme }) => css`
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-wrap: nowrap;
- justify-content: space-between;
- height: 100%;
-
- span[role='button'] {
- display: flex;
- height: 100%;
- }
-
- .title-panel {
- display: flex;
- align-items: center;
- min-width: 0;
- margin-right: ${theme.gridUnit * 12}px;
- }
-
- .right-button-panel {
- display: flex;
- align-items: center;
-
- > .btn-group {
- flex: 0 0 auto;
- margin-left: ${theme.gridUnit}px;
- }
- }
-
- .action-button {
- color: ${theme.colors.grayscale.base};
- margin: 0 ${theme.gridUnit * 1.5}px 0 ${theme.gridUnit}px;
- }
- `}
+const saveButtonStyles = theme => css`
+ color: ${theme.colors.primary.dark2};
+ & > span[role='img'] {
+ margin-right: 0;
+ }
`;
-const StyledButtons = styled.span`
- ${({ theme }) => css`
+const headerStyles = theme => css`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ height: 100%;
+
+ span[role='button'] {
+ display: flex;
+ height: 100%;
+ }
+
+ .title-panel {
display: flex;
align-items: center;
- padding-left: ${theme.gridUnit * 2}px;
+ min-width: 0;
+ margin-right: ${theme.gridUnit * 12}px;
+ }
- & .fave-unfave-icon {
- padding: 0 ${theme.gridUnit}px;
+ .right-button-panel {
+ display: flex;
+ align-items: center;
+ }
+`;
- &:first-child {
- padding-left: 0;
- }
+const buttonsStyles = theme => css`
+ display: flex;
+ align-items: center;
+ padding-left: ${theme.gridUnit * 2}px;
+
+ & .fave-unfave-icon {
+ padding: 0 ${theme.gridUnit}px;
+
+ &:first-child {
+ padding-left: 0;
}
- `}
+ }
+`;
+
+const saveButtonContainerStyles = theme => css`
+ margin-right: ${theme.gridUnit * 2}px;
`;
export class ExploreChartHeader extends React.PureComponent {
@@ -231,11 +231,13 @@ export class ExploreChartHeader extends React.PureComponent {
isStarred,
sliceUpdated,
sliceName,
+ onSaveChart,
+ saveDisabled,
} = this.props;
const { latestQueryFormData, sliceFormData } = chart;
const oldSliceName = slice?.slice_name;
return (
-