diff --git a/superset-frontend/src/dashboard/reducers/getInitialState.js b/superset-frontend/src/dashboard/reducers/getInitialState.js index 8aa0bb3c207ff..2e419dfd8c241 100644 --- a/superset-frontend/src/dashboard/reducers/getInitialState.js +++ b/superset-frontend/src/dashboard/reducers/getInitialState.js @@ -108,120 +108,116 @@ export default function (bootstrapData) { const sliceIds = new Set(); dashboard.slices.forEach(slice => { const key = slice.slice_id; - if ( - ['separator', 'markup', 'iframe'].indexOf(slice.form_data.viz_type) === -1 - ) { - const form_data = { - ...slice.form_data, - url_params: { - ...slice.form_data.url_params, - ...urlParams, - }, - }; - chartQueries[key] = { - ...chart, - id: key, - form_data, - formData: applyDefaultFormData(form_data), - }; - - slices[key] = { - slice_id: key, - slice_url: slice.slice_url, - slice_name: slice.slice_name, - form_data: slice.form_data, - edit_url: slice.edit_url, - viz_type: slice.form_data.viz_type, - datasource: slice.form_data.datasource, - description: slice.description, - description_markeddown: slice.description_markeddown, - owners: slice.owners, - modified: slice.modified, - changed_on: new Date(slice.changed_on).getTime(), - }; + const form_data = { + ...slice.form_data, + url_params: { + ...slice.form_data.url_params, + ...urlParams, + }, + }; + chartQueries[key] = { + ...chart, + id: key, + form_data, + formData: applyDefaultFormData(form_data), + }; - sliceIds.add(key); + slices[key] = { + slice_id: key, + slice_url: slice.slice_url, + slice_name: slice.slice_name, + form_data: slice.form_data, + edit_url: slice.edit_url, + viz_type: slice.form_data.viz_type, + datasource: slice.form_data.datasource, + description: slice.description, + description_markeddown: slice.description_markeddown, + owners: slice.owners, + modified: slice.modified, + changed_on: new Date(slice.changed_on).getTime(), + }; - // if there are newly added slices from explore view, fill slices into 1 or more rows - if (!chartIdToLayoutId[key] && layout[parentId]) { - if ( - newSlicesContainerWidth === 0 || - newSlicesContainerWidth + GRID_DEFAULT_CHART_WIDTH > GRID_COLUMN_COUNT - ) { - newSlicesContainer = newComponentFactory( - ROW_TYPE, - (parent.parents || []).slice(), - ); - layout[newSlicesContainer.id] = newSlicesContainer; - parent.children.push(newSlicesContainer.id); - newSlicesContainerWidth = 0; - } + sliceIds.add(key); - const chartHolder = newComponentFactory( - CHART_TYPE, - { - chartId: slice.slice_id, - }, - (newSlicesContainer.parents || []).slice(), + // if there are newly added slices from explore view, fill slices into 1 or more rows + if (!chartIdToLayoutId[key] && layout[parentId]) { + if ( + newSlicesContainerWidth === 0 || + newSlicesContainerWidth + GRID_DEFAULT_CHART_WIDTH > GRID_COLUMN_COUNT + ) { + newSlicesContainer = newComponentFactory( + ROW_TYPE, + (parent.parents || []).slice(), ); - - layout[chartHolder.id] = chartHolder; - newSlicesContainer.children.push(chartHolder.id); - chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id; - newSlicesContainerWidth += GRID_DEFAULT_CHART_WIDTH; + layout[newSlicesContainer.id] = newSlicesContainer; + parent.children.push(newSlicesContainer.id); + newSlicesContainerWidth = 0; } - // build DashboardFilters for interactive filter features - if (slice.form_data.viz_type === 'filter_box') { - const configs = getFilterConfigsFromFormdata(slice.form_data); - let columns = configs.columns; - const labels = configs.labels; - if (preselectFilters[key]) { - Object.keys(columns).forEach(col => { - if (preselectFilters[key][col]) { - columns = { - ...columns, - [col]: preselectFilters[key][col], - }; - } - }); - } + const chartHolder = newComponentFactory( + CHART_TYPE, + { + chartId: slice.slice_id, + }, + (newSlicesContainer.parents || []).slice(), + ); - const scopesByChartId = Object.keys(columns).reduce((map, column) => { - const scopeSettings = { - ...filterScopes[key], - }; - const { scope, immune } = { - ...DASHBOARD_FILTER_SCOPE_GLOBAL, - ...scopeSettings[column], - }; + layout[chartHolder.id] = chartHolder; + newSlicesContainer.children.push(chartHolder.id); + chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id; + newSlicesContainerWidth += GRID_DEFAULT_CHART_WIDTH; + } - return { - ...map, - [column]: { - scope, - immune, - }, - }; - }, {}); + // build DashboardFilters for interactive filter features + if (slice.form_data.viz_type === 'filter_box') { + const configs = getFilterConfigsFromFormdata(slice.form_data); + let columns = configs.columns; + const labels = configs.labels; + if (preselectFilters[key]) { + Object.keys(columns).forEach(col => { + if (preselectFilters[key][col]) { + columns = { + ...columns, + [col]: preselectFilters[key][col], + }; + } + }); + } - const componentId = chartIdToLayoutId[key]; - const directPathToFilter = (layout[componentId].parents || []).slice(); - directPathToFilter.push(componentId); - dashboardFilters[key] = { - ...dashboardFilter, - chartId: key, - componentId, - datasourceId: slice.form_data.datasource, - filterName: slice.slice_name, - directPathToFilter, - columns, - labels, - scopes: scopesByChartId, - isInstantFilter: !!slice.form_data.instant_filtering, - isDateFilter: Object.keys(columns).includes(TIME_RANGE), + const scopesByChartId = Object.keys(columns).reduce((map, column) => { + const scopeSettings = { + ...filterScopes[key], }; - } + const { scope, immune } = { + ...DASHBOARD_FILTER_SCOPE_GLOBAL, + ...scopeSettings[column], + }; + + return { + ...map, + [column]: { + scope, + immune, + }, + }; + }, {}); + + const componentId = chartIdToLayoutId[key]; + const directPathToFilter = (layout[componentId].parents || []).slice(); + directPathToFilter.push(componentId); + dashboardFilters[key] = { + ...dashboardFilter, + chartId: key, + componentId, + datasourceId: slice.form_data.datasource, + filterName: slice.slice_name, + directPathToFilter, + columns, + labels, + scopes: scopesByChartId, + isInstantFilter: !!slice.form_data.instant_filtering, + isDateFilter: Object.keys(columns).includes(TIME_RANGE), + }; } // sync layout names with current slice names in case a slice was edited diff --git a/superset-frontend/src/explore/components/SaveModal.jsx b/superset-frontend/src/explore/components/SaveModal.jsx index c97d4133cb086..804ebc2bc7522 100644 --- a/superset-frontend/src/explore/components/SaveModal.jsx +++ b/superset-frontend/src/explore/components/SaveModal.jsx @@ -33,8 +33,6 @@ import { CreatableSelect } from 'src/components/Select/SupersetStyledSelect'; import { t } from '@superset-ui/translation'; import ReactMarkdown from 'react-markdown'; -import { EXPLORE_ONLY_VIZ_TYPE } from '../constants'; - const propTypes = { can_overwrite: PropTypes.bool, onHide: PropTypes.func.isRequired, @@ -134,9 +132,6 @@ class SaveModal extends React.Component { this.setState({ alert: null }); } render() { - // do not add iframe/markdown/separator chart to dashboard - const canNotSaveToDash = - EXPLORE_ONLY_VIZ_TYPE.indexOf(this.state.vizType) > -1; return ( @@ -226,9 +221,7 @@ class SaveModal extends React.Component { id="btn_modal_save_goto_dash" bsSize="sm" disabled={ - canNotSaveToDash || - !this.state.newSliceName || - !this.state.newDashboardName + !this.state.newSliceName || !this.state.newDashboardName } onClick={this.saveOrOverwrite.bind(this, true)} > @@ -240,10 +233,7 @@ class SaveModal extends React.Component { bsSize="sm" bsStyle="primary" onClick={this.saveOrOverwrite.bind(this, false)} - disabled={ - !this.state.newSliceName || - (canNotSaveToDash && this.state.newDashboardName) - } + disabled={!this.state.newSliceName} > {t('Save')} diff --git a/superset-frontend/src/explore/constants.js b/superset-frontend/src/explore/constants.js index 1bf3cc8056577..b3d3a80043f81 100644 --- a/superset-frontend/src/explore/constants.js +++ b/superset-frontend/src/explore/constants.js @@ -71,8 +71,6 @@ export const sqlaAutoGeneratedMetricNameRegex = /^(sum|min|max|avg|count|count_d export const sqlaAutoGeneratedMetricRegex = /^(LONG|DOUBLE|FLOAT)?(SUM|AVG|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i; export const druidAutoGeneratedMetricRegex = /^(LONG|DOUBLE|FLOAT)?(SUM|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i; -export const EXPLORE_ONLY_VIZ_TYPE = ['separator', 'markup', 'iframe']; - export const TIME_FILTER_LABELS = { time_range: t('Time Range'), granularity_sqla: t('Time Column'), diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index cf582cb1d2e17..3fcad1ab7e982 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -29,9 +29,7 @@ import ForceDirectedChartPlugin from '@superset-ui/legacy-plugin-chart-force-dir import HeatmapChartPlugin from '@superset-ui/legacy-plugin-chart-heatmap'; import HistogramChartPlugin from '@superset-ui/legacy-plugin-chart-histogram'; import HorizonChartPlugin from '@superset-ui/legacy-plugin-chart-horizon'; -import IframeChartPlugin from '@superset-ui/legacy-plugin-chart-iframe'; import MapBoxChartPlugin from '@superset-ui/legacy-plugin-chart-map-box'; -import MarkupChartPlugin from '@superset-ui/legacy-plugin-chart-markup'; import PairedTTestChartPlugin from '@superset-ui/legacy-plugin-chart-paired-t-test'; import ParallelCoordinatesChartPlugin from '@superset-ui/legacy-plugin-chart-parallel-coordinates'; import PartitionChartPlugin from '@superset-ui/legacy-plugin-chart-partition'; @@ -87,12 +85,9 @@ export default class MainPreset extends Preset { new HeatmapChartPlugin().configure({ key: 'heatmap' }), new HistogramChartPlugin().configure({ key: 'histogram' }), new HorizonChartPlugin().configure({ key: 'horizon' }), - new IframeChartPlugin().configure({ key: 'iframe' }), new LineChartPlugin().configure({ key: 'line' }), new LineMultiChartPlugin().configure({ key: 'line_multi' }), new MapBoxChartPlugin().configure({ key: 'mapbox' }), - new MarkupChartPlugin().configure({ key: 'markup' }), - new MarkupChartPlugin().configure({ key: 'separator' }), new PairedTTestChartPlugin().configure({ key: 'paired_ttest' }), new ParallelCoordinatesChartPlugin().configure({ key: 'para' }), new PartitionChartPlugin().configure({ key: 'partition' }), diff --git a/superset/migrations/versions/978245563a02_migrate_iframe_to_dash_markdown.py b/superset/migrations/versions/978245563a02_migrate_iframe_to_dash_markdown.py index e927d2489b082..b5fffc15e9684 100644 --- a/superset/migrations/versions/978245563a02_migrate_iframe_to_dash_markdown.py +++ b/superset/migrations/versions/978245563a02_migrate_iframe_to_dash_markdown.py @@ -58,6 +58,13 @@ class Slice(Base): Column("slice_id", Integer, ForeignKey("slices.id")), ) +slice_user = Table( + "slice_user", + Base.metadata, + Column("id", Integer, primary_key=True), + Column("user_id", Integer, ForeignKey("ab_user.id")), + Column("slice_id", Integer, ForeignKey("slices.id")), +) class Dashboard(Base): __tablename__ = "dashboards" @@ -153,6 +160,27 @@ def upgrade(): sort_keys=True, ) session.merge(dashboard) + + # remove iframe, separator and markup charts + slices_to_remove = session.query(Slice).filter( + Slice.viz_type.in_(["iframe", "separator", "markup"]) + ).all() + slices_ids = [slc.id for slc in slices_to_remove] + + # remove dependencies first + session.query(dashboard_slices).filter( + dashboard_slices.c.slice_id.in_(slices_ids) + ).delete(synchronize_session=False) + + session.query(slice_user).filter( + slice_user.c.slice_id.in_(slices_ids) + ).delete(synchronize_session=False) + + # remove slices + session.query(Slice).filter( + Slice.id.in_(slices_ids) + ).delete(synchronize_session=False) + except Exception as ex: logging.exception(f"dashboard {dashboard.id} has error: {ex}") diff --git a/superset/migrations/versions/f80a3b88324b_.py b/superset/migrations/versions/f80a3b88324b_.py new file mode 100644 index 0000000000000..7defdc5e2be45 --- /dev/null +++ b/superset/migrations/versions/f80a3b88324b_.py @@ -0,0 +1,38 @@ +# 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. +"""empty message + +Revision ID: f80a3b88324b +Revises: ('978245563a02', 'f120347acb39') +Create Date: 2020-08-12 15:47:56.580191 + +""" + +# revision identifiers, used by Alembic. +revision = "f80a3b88324b" +down_revision = ("978245563a02", "f120347acb39") + +import sqlalchemy as sa +from alembic import op + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/superset/viz.py b/superset/viz.py index 88d67bb03b0c0..2273bce70510d 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -2040,25 +2040,6 @@ def get_data(self, df: pd.DataFrame) -> VizData: return d -class IFrameViz(BaseViz): - - """You can squeeze just about anything in this iFrame component""" - - viz_type = "iframe" - verbose_name = _("iFrame") - credits = 'a Superset original' - is_timeseries = False - - def query_obj(self) -> QueryObjectDict: - return {} - - def get_df(self, query_obj: Optional[QueryObjectDict] = None) -> pd.DataFrame: - return pd.DataFrame() - - def get_data(self, df: pd.DataFrame) -> VizData: - return {"iframe": True} - - class ParallelCoordinatesViz(BaseViz): """Interactive parallel coordinate implementation