fix: Color consistency (#17089)

* Update label colors on the fly

* Clean up

* Improve getFormDataWithExtraFilters

* Improve code structure

* Remove labelColors from formData

* Exclude label_colors from URL

* Refactor color scheme implementation

* Clean up

* Refactor and simplify

* Fix lint

* Remove unnecessary ColorMapControl

* Lint

* Give json color scheme precedence

* Add label_colors prop in metadata

* Separate owners and dashboard meta requests

* Remove label_colors control

* bump superset-ui 0.18.19

* Fix end of file

* Update tests

* Fix lint

* Update Cypress

* Update setColorScheme method

* Use Antd modal body
This commit is contained in:
Geido 2021-11-03 19:22:38 +02:00 committed by GitHub
parent 85a19a9cc2
commit 59a6502efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 488 additions and 500 deletions

View File

@ -24,10 +24,10 @@ import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
function selectColorScheme(color: string) {
// open color scheme dropdown
cy.get('.modal-body')
.contains('Color Scheme')
cy.get('.ant-modal-body')
.contains('Color scheme')
.parents('.ControlHeader')
.next('.Select')
.next('.ant-select')
.click()
.then($colorSelect => {
// select a new color scheme
@ -37,7 +37,7 @@ function selectColorScheme(color: string) {
function assertMetadata(text: string) {
const regex = new RegExp(text);
cy.get('.modal-body')
cy.get('.ant-modal-body')
.find('#json_metadata')
.should('be.visible')
.then(() => {
@ -50,12 +50,15 @@ function assertMetadata(text: string) {
}
function typeMetadata(text: string) {
cy.get('.modal-body').find('#json_metadata').should('be.visible').type(text);
cy.get('.ant-modal-body')
.find('#json_metadata')
.should('be.visible')
.type(text);
}
function openAdvancedProperties() {
return cy
.get('.modal-body')
.get('.ant-modal-body')
.contains('Advanced')
.should('be.visible')
.click();
@ -94,6 +97,10 @@ describe('Dashboard edit action', () => {
.get('[data-test="dashboard-title-input"]')
.type(`{selectall}{backspace}${dashboardTitle}`);
cy.wait('@dashboardGet').then(() => {
selectColorScheme('d3Category20b');
});
// save edit changes
cy.get('.ant-modal-footer')
.contains('Save')
@ -146,7 +153,7 @@ describe('Dashboard edit action', () => {
.click()
.then(() => {
// assert that modal edit window has closed
cy.get('.modal-body').should('not.exist');
cy.get('.ant-modal-body').should('not.exist');
// assert color has been updated
openDashboardEditProperties();
@ -177,7 +184,7 @@ describe('Dashboard edit action', () => {
.click()
.then(() => {
// assert that modal edit window has closed
cy.get('.modal-body')
cy.get('.ant-modal-body')
.contains('A valid color scheme is required')
.should('be.visible');
});

File diff suppressed because it is too large Load Diff

View File

@ -68,35 +68,35 @@
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@superset-ui/chart-controls": "^0.18.18",
"@superset-ui/core": "^0.18.18",
"@superset-ui/legacy-plugin-chart-calendar": "^0.18.18",
"@superset-ui/legacy-plugin-chart-chord": "^0.18.18",
"@superset-ui/legacy-plugin-chart-country-map": "^0.18.18",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.18.18",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.18.18",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.18.18",
"@superset-ui/legacy-plugin-chart-histogram": "^0.18.18",
"@superset-ui/legacy-plugin-chart-horizon": "^0.18.18",
"@superset-ui/legacy-plugin-chart-map-box": "^0.18.18",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.18.18",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.18.18",
"@superset-ui/legacy-plugin-chart-partition": "^0.18.18",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.18.18",
"@superset-ui/legacy-plugin-chart-rose": "^0.18.18",
"@superset-ui/legacy-plugin-chart-sankey": "^0.18.18",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.18.18",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.18",
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.18",
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.18",
"@superset-ui/legacy-preset-chart-big-number": "^0.18.18",
"@superset-ui/chart-controls": "^0.18.19",
"@superset-ui/core": "^0.18.19",
"@superset-ui/legacy-plugin-chart-calendar": "^0.18.19",
"@superset-ui/legacy-plugin-chart-chord": "^0.18.19",
"@superset-ui/legacy-plugin-chart-country-map": "^0.18.19",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.18.19",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.18.19",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.18.19",
"@superset-ui/legacy-plugin-chart-histogram": "^0.18.19",
"@superset-ui/legacy-plugin-chart-horizon": "^0.18.19",
"@superset-ui/legacy-plugin-chart-map-box": "^0.18.19",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.18.19",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.18.19",
"@superset-ui/legacy-plugin-chart-partition": "^0.18.19",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.18.19",
"@superset-ui/legacy-plugin-chart-rose": "^0.18.19",
"@superset-ui/legacy-plugin-chart-sankey": "^0.18.19",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.18.19",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.19",
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.19",
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.19",
"@superset-ui/legacy-preset-chart-big-number": "^0.18.19",
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.18",
"@superset-ui/plugin-chart-echarts": "^0.18.18",
"@superset-ui/plugin-chart-pivot-table": "^0.18.18",
"@superset-ui/plugin-chart-table": "^0.18.18",
"@superset-ui/plugin-chart-word-cloud": "^0.18.18",
"@superset-ui/preset-chart-xy": "^0.18.18",
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.19",
"@superset-ui/plugin-chart-echarts": "^0.18.19",
"@superset-ui/plugin-chart-pivot-table": "^0.18.19",
"@superset-ui/plugin-chart-table": "^0.18.19",
"@superset-ui/plugin-chart-word-cloud": "^0.18.19",
"@superset-ui/preset-chart-xy": "^0.18.19",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.9.4",

View File

@ -94,10 +94,12 @@ describe('PropertiesModal', () => {
describe('without metadata', () => {
const wrapper = setup({ colorScheme: 'SUPERSET_DEFAULT' });
const modalInstance = wrapper.find('PropertiesModal').instance();
it('does not update the color scheme in the metadata', () => {
it('updates the color scheme in the metadata', () => {
const spy = jest.spyOn(modalInstance, 'onMetadataChange');
modalInstance.onColorSchemeChange('SUPERSET_DEFAULT');
expect(spy).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(
'{"something": "foo", "color_scheme": "SUPERSET_DEFAULT", "label_colors": {}}',
);
});
});
describe('with metadata', () => {
@ -125,10 +127,12 @@ describe('PropertiesModal', () => {
json_metadata: '{"timed_refresh_immune_slices": []}',
},
});
it('will not update the metadata', () => {
it('will update the metadata', () => {
const spy = jest.spyOn(modalInstance, 'onMetadataChange');
modalInstance.onColorSchemeChange('SUPERSET_DEFAULT');
expect(spy).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(
'{"something": "foo", "color_scheme": "SUPERSET_DEFAULT", "label_colors": {}}',
);
});
});
});

View File

@ -44,6 +44,7 @@ const propTypes = {
// formData contains chart's own filter parameter
// and merged with extra filter that current dashboard applying
formData: PropTypes.object.isRequired,
labelColors: PropTypes.object,
width: PropTypes.number,
height: PropTypes.number,
setControlValue: PropTypes.func,

View File

@ -29,6 +29,7 @@ const propTypes = {
datasource: PropTypes.object,
initialValues: PropTypes.object,
formData: PropTypes.object.isRequired,
labelColors: PropTypes.object,
height: PropTypes.number,
width: PropTypes.number,
setControlValue: PropTypes.func,
@ -100,6 +101,7 @@ class ChartRenderer extends React.Component {
nextProps.height !== this.props.height ||
nextProps.width !== this.props.width ||
nextProps.triggerRender ||
nextProps.labelColors !== this.props.labelColors ||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
);

View File

@ -17,13 +17,32 @@
* under the License.
*/
import { Dispatch } from 'redux';
import { makeApi } from '@superset-ui/core';
import { makeApi, CategoricalColorNamespace } from '@superset-ui/core';
import { isString } from 'lodash';
import { ChartConfiguration, DashboardInfo } from '../reducers/types';
export const DASHBOARD_INFO_UPDATED = 'DASHBOARD_INFO_UPDATED';
// updates partially changed dashboard info
export function dashboardInfoChanged(newInfo: { metadata: any }) {
const { metadata } = newInfo;
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
metadata?.color_namespace,
);
categoricalNamespace.resetColors();
if (metadata?.label_colors) {
const labelColors = metadata.label_colors;
const colorMap = isString(labelColors)
? JSON.parse(labelColors)
: labelColors;
Object.keys(colorMap).forEach(label => {
categoricalNamespace.setColor(label, colorMap[label]);
});
}
return { type: DASHBOARD_INFO_UPDATED, newInfo };
}
export const SET_CHART_CONFIG_BEGIN = 'SET_CHART_CONFIG_BEGIN';

View File

@ -88,16 +88,16 @@ export const hydrateDashboard = (dashboardData, chartData) => (
// Priming the color palette with user's label-color mapping provided in
// the dashboard's JSON metadata
if (metadata?.label_colors) {
const scheme = metadata.color_scheme;
const namespace = metadata.color_namespace;
const colorMap = isString(metadata.label_colors)
? JSON.parse(metadata.label_colors)
: metadata.label_colors;
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
namespace,
);
Object.keys(colorMap).forEach(label => {
CategoricalColorNamespace.getScale(scheme, namespace).setColor(
label,
colorMap[label],
);
categoricalNamespace.setColor(label, colorMap[label]);
});
}

View File

@ -20,7 +20,7 @@
import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
import { styled, CategoricalColorNamespace, t } from '@superset-ui/core';
import { styled, t } from '@superset-ui/core';
import ButtonGroup from 'src/components/ButtonGroup';
import {
@ -348,19 +348,10 @@ class Header extends React.PureComponent {
lastModifiedTime,
} = this.props;
const scale = CategoricalColorNamespace.getScale(
colorScheme,
colorNamespace,
);
// use the colorScheme for default labels
let labelColors = colorScheme ? scale.getColorMap() : {};
// but allow metadata to overwrite if it exists
// eslint-disable-next-line camelcase
const metadataLabelColors = dashboardInfo.metadata?.label_colors;
if (metadataLabelColors) {
labelColors = { ...labelColors, ...metadataLabelColors };
}
const labelColors =
colorScheme && dashboardInfo?.metadata?.label_colors
? dashboardInfo.metadata.label_colors
: {};
// check refresh frequency is for current session or persist
const refreshFrequency = shouldPersistRefreshFrequency

View File

@ -29,7 +29,6 @@ import {
t,
SupersetClient,
getCategoricalSchemeRegistry,
CategoricalColorNamespace,
} from '@superset-ui/core';
import Modal from 'src/components/Modal';
@ -140,14 +139,15 @@ class PropertiesModal extends React.PureComponent {
JsonEditor.preload();
}
onColorSchemeChange(value, { updateMetadata = true } = {}) {
onColorSchemeChange(colorScheme, { updateMetadata = true } = {}) {
// check that color_scheme is valid
const colorChoices = getCategoricalSchemeRegistry().keys();
const { json_metadata: jsonMetadata } = this.state.values;
const jsonMetadataObj = jsonMetadata?.length
? JSON.parse(jsonMetadata)
: {};
if (!colorChoices.includes(value)) {
if (!colorScheme || !colorChoices.includes(colorScheme)) {
Modal.error({
title: 'Error',
content: t('A valid color scheme is required'),
@ -157,24 +157,14 @@ class PropertiesModal extends React.PureComponent {
}
// update metadata to match selection
if (
updateMetadata &&
Object.keys(jsonMetadataObj).includes('color_scheme')
) {
jsonMetadataObj.color_scheme = value;
jsonMetadataObj.label_colors = Object.keys(
jsonMetadataObj.label_colors ?? {},
).reduce(
(prev, next) => ({
...prev,
[next]: CategoricalColorNamespace.getScale(value)(next),
}),
{},
);
if (updateMetadata) {
jsonMetadataObj.color_scheme = colorScheme;
jsonMetadataObj.label_colors = jsonMetadataObj.label_colors || {};
this.onMetadataChange(jsonStringify(jsonMetadataObj));
}
this.updateFormState('colorScheme', value);
this.updateFormState('colorScheme', colorScheme);
}
onOwnersChange(value) {
@ -261,21 +251,22 @@ class PropertiesModal extends React.PureComponent {
roles: rolesValue,
},
} = this.state;
const { onlyApply } = this.props;
const owners = ownersValue?.map(o => o.value) ?? [];
const roles = rolesValue?.map(o => o.value) ?? [];
let metadataColorScheme;
let currentColorScheme = colorScheme;
// update color scheme to match metadata
// color scheme in json metadata has precedence over selection
if (jsonMetadata?.length) {
const { color_scheme: metadataColorScheme } = JSON.parse(jsonMetadata);
if (metadataColorScheme) {
this.onColorSchemeChange(metadataColorScheme, {
updateMetadata: false,
});
}
const metadata = JSON.parse(jsonMetadata);
currentColorScheme = metadata?.color_scheme || colorScheme;
}
this.onColorSchemeChange(currentColorScheme, {
updateMetadata: false,
});
const moreProps = {};
const morePutProps = {};
if (isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC)) {
@ -289,7 +280,7 @@ class PropertiesModal extends React.PureComponent {
slug,
jsonMetadata,
ownerIds: owners,
colorScheme: metadataColorScheme || colorScheme,
colorScheme: currentColorScheme,
...moreProps,
});
this.props.onHide();
@ -316,7 +307,7 @@ class PropertiesModal extends React.PureComponent {
slug: result.slug,
jsonMetadata: result.json_metadata,
ownerIds: result.owners,
colorScheme: metadataColorScheme || colorScheme,
colorScheme: currentColorScheme,
...moreResultProps,
});
this.props.onHide();

View File

@ -21,7 +21,7 @@ import React from 'react';
import { Radio } from 'src/components/Radio';
import { RadioChangeEvent, Input } from 'src/common/components';
import Button from 'src/components/Button';
import { t, CategoricalColorNamespace, JsonResponse } from '@superset-ui/core';
import { t, JsonResponse } from '@superset-ui/core';
import ModalTrigger from 'src/components/ModalTrigger';
import Checkbox from 'src/components/Checkbox';
@ -131,11 +131,11 @@ class SaveModal extends React.PureComponent<SaveModalProps, SaveModalState> {
lastModifiedTime,
} = this.props;
const scale = CategoricalColorNamespace.getScale(
colorScheme,
colorNamespace,
);
const labelColors = colorScheme ? scale.getColorMap() : {};
const labelColors =
colorScheme && dashboardInfo?.metadata?.label_colors
? dashboardInfo.metadata.label_colors
: {};
// check refresh frequency is for current session or persist
const refreshFrequency = shouldPersistRefreshFrequency
? currentRefreshFrequency

View File

@ -55,6 +55,7 @@ const propTypes = {
// from redux
chart: chartPropShape.isRequired,
formData: PropTypes.object.isRequired,
labelColors: PropTypes.object,
datasource: PropTypes.object,
slice: slicePropShape.isRequired,
sliceName: PropTypes.string.isRequired,
@ -277,6 +278,7 @@ export default class Chart extends React.Component {
editMode,
filters,
formData,
labelColors,
updateSliceName,
sliceName,
toggleExpandSlice,
@ -399,6 +401,7 @@ export default class Chart extends React.Component {
dashboardId={dashboardId}
initialValues={initialValues}
formData={formData}
labelColors={labelColors}
ownState={ownState}
filterState={filterState}
queriesResponse={chart.queriesResponse}

View File

@ -61,7 +61,7 @@ function mapStateToProps(
(chart && chart.form_data && datasources[chart.form_data.datasource]) ||
PLACEHOLDER_DATASOURCE;
const { colorScheme, colorNamespace } = dashboardState;
const labelColors = dashboardInfo?.metadata?.label_colors || {};
// note: this method caches filters if possible to prevent render cascades
const formData = getFormDataWithExtraFilters({
layout: dashboardLayout.present,
@ -75,6 +75,7 @@ function mapStateToProps(
sliceId: id,
nativeFilters,
dataMask,
labelColors,
});
formData.dashboardId = dashboardInfo.id;
@ -82,6 +83,7 @@ function mapStateToProps(
return {
chart,
datasource,
labelColors,
slice: sliceEntities.slices[id],
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
filters: getActiveFilters() || EMPTY_OBJECT,

View File

@ -16,11 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
CategoricalColorNamespace,
DataRecordFilters,
JsonObject,
} from '@superset-ui/core';
import { DataRecordFilters, JsonObject } from '@superset-ui/core';
import { ChartQueryPayload, Charts, LayoutItem } from 'src/dashboard/types';
import { getExtraFormData } from 'src/dashboard/components/nativeFilters/utils';
import { DataMaskStateWithId } from 'src/dataMask/types';
@ -45,6 +41,7 @@ export interface GetFormDataWithExtraFiltersArguments {
sliceId: number;
dataMask: DataMaskStateWithId;
nativeFilters: NativeFiltersState;
labelColors?: Record<string, string>;
}
// this function merge chart's formData with dashboard filters value,
@ -61,11 +58,8 @@ export default function getFormDataWithExtraFilters({
sliceId,
layout,
dataMask,
labelColors,
}: GetFormDataWithExtraFiltersArguments) {
// Propagate color mapping to chart
const scale = CategoricalColorNamespace.getScale(colorScheme, colorNamespace);
const labelColors = scale.getColorMap();
// if dashboard metadata + filters have not changed, use cache if possible
const cachedFormData = cachedFormdataByChart[sliceId];
if (
@ -109,11 +103,12 @@ export default function getFormDataWithExtraFilters({
const formData = {
...chart.formData,
...(colorScheme && { color_scheme: colorScheme }),
label_colors: labelColors,
...(colorScheme && { color_scheme: colorScheme }),
extra_filters: getEffectiveExtraFilters(filters),
...extraData,
};
cachedFiltersByChart[sliceId] = filters;
cachedFormdataByChart[sliceId] = { ...formData, dataMask };

View File

@ -71,7 +71,9 @@ export default function Control(props: ControlProps) {
const ControlComponent = typeof type === 'string' ? controlMap[type] : type;
if (!ControlComponent) {
return <>Unknown controlType: {type}</>;
// eslint-disable-next-line no-console
console.warn(`Unknown controlType: ${type}`);
return null;
}
return (

View File

@ -21,7 +21,12 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import Icons from 'src/components/Icons';
import { styled, t } from '@superset-ui/core';
import {
CategoricalColorNamespace,
SupersetClient,
styled,
t,
} from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import ReportModal from 'src/components/ReportModal';
import {
@ -53,6 +58,7 @@ const propTypes = {
addHistory: PropTypes.func,
can_overwrite: PropTypes.bool.isRequired,
can_download: PropTypes.bool.isRequired,
dashboardId: PropTypes.number,
isStarred: PropTypes.bool.isRequired,
slice: PropTypes.object,
sliceName: PropTypes.string,
@ -114,9 +120,11 @@ export class ExploreChartHeader extends React.PureComponent {
this.showReportModal = this.showReportModal.bind(this);
this.hideReportModal = this.hideReportModal.bind(this);
this.renderReportModal = this.renderReportModal.bind(this);
this.fetchChartDashboardData = this.fetchChartDashboardData.bind(this);
}
componentDidMount() {
const { dashboardId } = this.props;
if (this.canAddReports()) {
const { user, chart } = this.props;
// this is in the case that there is an anonymous user.
@ -127,6 +135,33 @@ export class ExploreChartHeader extends React.PureComponent {
chart.id,
);
}
if (dashboardId) {
this.fetchChartDashboardData();
}
}
async fetchChartDashboardData() {
const { dashboardId, slice } = this.props;
const response = await SupersetClient.get({
endpoint: `/api/v1/chart/${slice.slice_id}`,
});
const chart = response.json.result;
const dashboards = chart.dashboards || [];
const dashboard =
dashboardId &&
dashboards.length &&
dashboards.find(d => d.id === dashboardId);
if (dashboard && dashboard.json_metadata) {
// setting the chart to use the dashboard custom label colors if any
const labelColors =
JSON.parse(dashboard.json_metadata).label_colors || {};
const categoricalNamespace = CategoricalColorNamespace.getNamespace();
Object.keys(labelColors).forEach(label => {
categoricalNamespace.setColor(label, labelColors[label]);
});
}
}
getSliceName() {

View File

@ -38,6 +38,7 @@ const propTypes = {
can_overwrite: PropTypes.bool.isRequired,
can_download: PropTypes.bool.isRequired,
datasource: PropTypes.object,
dashboardId: PropTypes.number,
column_formats: PropTypes.object,
containerId: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
@ -291,6 +292,7 @@ const ExploreChartPanel = props => {
addHistory={props.addHistory}
can_overwrite={props.can_overwrite}
can_download={props.can_download}
dashboardId={props.dashboardId}
isStarred={props.isStarred}
slice={props.slice}
sliceName={props.sliceName}

View File

@ -591,6 +591,7 @@ function mapStateToProps(state) {
);
const chartKey = Object.keys(charts)[0];
const chart = charts[chartKey];
return {
isDatasourceMetaLoading: explore.isDatasourceMetaLoading,
datasource: explore.datasource,

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import React, { useMemo, useState, useCallback, useEffect } from 'react';
import Modal from 'src/components/Modal';
import { Row, Col, Input, TextArea } from 'src/common/components';
import Button from 'src/components/Button';
@ -33,6 +33,8 @@ type PropertiesModalProps = {
show: boolean;
onHide: () => void;
onSave: (chart: Chart) => void;
permissionsError?: string;
existingOwners?: SelectValue;
};
export default function PropertiesModal({
@ -42,16 +44,15 @@ export default function PropertiesModal({
show,
}: PropertiesModalProps) {
const [submitting, setSubmitting] = useState(false);
const [selectedOwners, setSelectedOwners] = useState<SelectValue | null>(
null,
);
// values of form inputs
const [name, setName] = useState(slice.slice_name || '');
const [description, setDescription] = useState(slice.description || '');
const [cacheTimeout, setCacheTimeout] = useState(
slice.cache_timeout != null ? slice.cache_timeout : '',
);
const [selectedOwners, setSelectedOwners] = useState<SelectValue | null>(
null,
);
function showError({ error, statusText, message }: any) {
let errorText = error || statusText || t('An error has occurred');
@ -65,8 +66,8 @@ export default function PropertiesModal({
});
}
const fetchChartData = useCallback(
async function fetchChartData() {
const fetchChartOwners = useCallback(
async function fetchChartOwners() {
try {
const response = await SupersetClient.get({
endpoint: `/api/v1/chart/${slice.slice_id}`,
@ -143,8 +144,8 @@ export default function PropertiesModal({
// get the owners of this slice
useEffect(() => {
fetchChartData();
}, [fetchChartData]);
fetchChartOwners();
}, [fetchChartOwners]);
// update name after it's changed in another modal
useEffect(() => {
@ -242,8 +243,8 @@ export default function PropertiesModal({
mode="multiple"
name="owners"
value={selectedOwners || []}
options={loadOptions}
onChange={setSelectedOwners}
options={loadOptions}
disabled={!selectedOwners}
allowClear
/>

View File

@ -1,54 +0,0 @@
/**
* 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 PropTypes from 'prop-types';
import React from 'react';
import { CategoricalColorNamespace } from '@superset-ui/core';
const propTypes = {
onChange: PropTypes.func,
value: PropTypes.object,
colorScheme: PropTypes.string,
colorNamespace: PropTypes.string,
};
const defaultProps = {
onChange: () => {},
value: {},
colorScheme: undefined,
colorNamespace: undefined,
};
export default class ColorMapControl extends React.PureComponent {
constructor(props) {
super(props);
Object.keys(this.props.value).forEach(label => {
CategoricalColorNamespace.getScale(
this.props.colorScheme,
this.props.colorNamespace,
).setColor(label, this.props.value[label]);
});
}
render() {
return null;
}
}
ColorMapControl.propTypes = propTypes;
ColorMapControl.defaultProps = defaultProps;

View File

@ -21,7 +21,6 @@ import AnnotationLayerControl from './AnnotationLayerControl';
import BoundsControl from './BoundsControl';
import CheckboxControl from './CheckboxControl';
import CollectionControl from './CollectionControl';
import ColorMapControl from './ColorMapControl';
import ColorPickerControl from './ColorPickerControl';
import ColorSchemeControl from './ColorSchemeControl';
import DatasourceControl from './DatasourceControl';
@ -52,7 +51,6 @@ const controlMap = {
BoundsControl,
CheckboxControl,
CollectionControl,
ColorMapControl,
ColorPickerControl,
ColorSchemeControl,
DatasourceControl,

View File

@ -77,7 +77,7 @@ export const datasourceAndVizType: ControlPanelSectionConfig = {
export const colorScheme: ControlPanelSectionConfig = {
label: t('Color scheme'),
controlSetRows: [['color_scheme', 'label_colors']],
controlSetRows: [['color_scheme']],
};
export const sqlaTimeSeries: ControlPanelSectionConfig = {

View File

@ -476,15 +476,4 @@ export const controls = {
description: t('The color scheme for rendering chart'),
schemes: () => categoricalSchemeRegistry.getMap(),
},
label_colors: {
type: 'ColorMapControl',
label: t('Color map'),
default: {},
renderTrigger: true,
mapStateToProps: state => ({
colorNamespace: state.form_data.color_namespace,
colorScheme: state.form_data.color_scheme,
}),
},
};

View File

@ -132,7 +132,11 @@ export function getExploreUrlFromDashboard(formData) {
// These are present when generating explore urls from the dashboard page.
// This should be superseded by some sort of "exploration context" system
// where form data and other context is referenced by id.
const trimmedFormData = omit(formData, ['dataMask', 'url_params']);
const trimmedFormData = omit(formData, [
'dataMask',
'url_params',
'label_colors',
]);
return getExploreLongUrl(trimmedFormData, null, false);
}
@ -169,6 +173,11 @@ export function getExploreUrl({
if (!formData.datasource) {
return null;
}
// label_colors should not pollute the URL
// eslint-disable-next-line no-param-reassign
delete formData.label_colors;
let uri = getChartDataUri({ path: '/', allowDomainSharding });
if (curUrl) {
uri = URI(URI(curUrl).search());

View File

@ -17,7 +17,7 @@
* under the License.
*/
import { getChartControlPanelRegistry, t } from '@superset-ui/core';
import { getChartControlPanelRegistry } from '@superset-ui/core';
import getControlsForVizType from 'src/utils/getControlsForVizType';
const fakePluginControls = {
@ -26,7 +26,6 @@ const fakePluginControls = {
label: 'Fake Control Panel Sections',
expanded: true,
controlSetRows: [
['label_colors'],
[
{
name: 'y_axis_bounds',
@ -81,16 +80,6 @@ describe('getControlsForVizType', () => {
JSON.stringify(getControlsForVizType('chart_controls_inventory_fake')),
).toEqual(
JSON.stringify({
label_colors: {
type: 'ColorMapControl',
label: t('Color map'),
default: {},
renderTrigger: true,
mapStateToProps: state => ({
colorNamespace: state.form_data.color_namespace,
colorScheme: state.form_data.color_scheme,
}),
},
y_axis_bounds: {
type: 'BoundsControl',
label: 'Value bounds',

View File

@ -123,6 +123,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
"cache_timeout",
"dashboards.dashboard_title",
"dashboards.id",
"dashboards.json_metadata",
"description",
"owners.first_name",
"owners.id",