diff --git a/.github/workflows/superset-cli.yml b/.github/workflows/superset-cli.yml new file mode 100644 index 0000000000000..369447e1509c5 --- /dev/null +++ b/.github/workflows/superset-cli.yml @@ -0,0 +1,76 @@ +name: Superset CLI tests + +on: + push: + branches-ignore: + - "dependabot/npm_and_yarn/**" + pull_request: + types: [synchronize, opened, reopened, ready_for_review] + +jobs: + test-load-examples: + if: github.event.pull_request.draft == false + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.9] + env: + PYTHONPATH: ${{ github.workspace }} + SUPERSET_CONFIG: tests.integration_tests.superset_test_config + REDIS_PORT: 16379 + SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_USER: superset + POSTGRES_PASSWORD: superset + ports: + # Use custom ports for services to avoid accidentally connecting to + # GitHub action runner's default installations + - 15432:5432 + redis: + image: redis:5-alpine + ports: + - 16379:6379 + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v2 + with: + persist-credentials: false + submodules: recursive + - name: Check if python changes are present + id: check + env: + GITHUB_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + continue-on-error: true + run: ./scripts/ci_check_no_file_changes.sh python + - name: Setup Python + if: steps.check.outcome == 'failure' + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'requirements/testing.txt' + - name: Install dependencies + if: steps.check.outcome == 'failure' + uses: ./.github/actions/cached-dependencies + with: + run: | + apt-get-install + pip-upgrade + pip install wheel + pip install -r requirements/testing.txt + setup-postgres + - name: superset init + if: steps.check.outcome == 'failure' + run: | + pip install -e . + superset db upgrade + superset load_test_users + - name: superset load_examples + if: steps.check.outcome == 'failure' + run: | + # load examples without test data + superset load_examples --load-big-data diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts index 8d29d20d091dc..24eab4284f490 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts @@ -155,7 +155,7 @@ export function resize(selector: string) { return { to(cordX: number, cordY: number) { cy.get(selector) - .trigger('mousedown', { which: 1 }) + .trigger('mousedown', { which: 1, force: true }) .trigger('mousemove', { which: 1, cordX, cordY, force: true }) .trigger('mouseup', { which: 1, force: true }); }, diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts index fe908e1f4a105..7ce391868809b 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts @@ -61,7 +61,7 @@ describe('Dashboard edit markdown', () => { cy.get('[data-test="dashboard-markdown-editor"]').contains('Test resize'); - cy.get('@component-background-first').click('right'); + cy.get('[data-test="nav-list"]:first').click('right', { force: true }); cy.get('[data-test="dashboard-component-chart-holder"]') .find('.ace_content') .should('not.exist'); diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 614bfeb0cae19..6a05e4d9942bc 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -156,7 +156,6 @@ class ChartEntityResponseSchema(Schema): slice_name = fields.String(description=slice_name_description) cache_timeout = fields.Integer(description=cache_timeout_description) changed_on = fields.String(description=changed_on_description) - modified = fields.String() description = fields.String(description=description_description) description_markeddown = fields.String( description=description_markeddown_description diff --git a/superset/cli/examples.py b/superset/cli/examples.py index 26ed9451f0765..cad87da9d3d25 100755 --- a/superset/cli/examples.py +++ b/superset/cli/examples.py @@ -54,8 +54,7 @@ def load_examples_run( if load_test_data: print("Loading [Tabbed dashboard]") examples.load_tabbed_dashboard(only_metadata) - - if not load_test_data: + else: print("Loading [Random long/lat data]") examples.load_long_lat_data(only_metadata, force) @@ -105,8 +104,8 @@ def load_examples_run( help="Force load data even if table already exists", ) def load_examples( - load_test_data: bool, - load_big_data: bool, + load_test_data: bool = False, + load_big_data: bool = False, only_metadata: bool = False, force: bool = False, ) -> None: diff --git a/superset/examples/big_data.py b/superset/examples/big_data.py index f837effd34bc4..8c0f2e267c555 100644 --- a/superset/examples/big_data.py +++ b/superset/examples/big_data.py @@ -30,7 +30,7 @@ sqlalchemy.sql.sqltypes.FLOAT(), sqlalchemy.sql.sqltypes.DATE(), sqlalchemy.sql.sqltypes.TIME(), - sqlalchemy.sql.sqltypes.DATETIME(), + sqlalchemy.sql.sqltypes.TIMESTAMP(), ] @@ -72,5 +72,5 @@ def load_big_data() -> None: add_data(columns=columns, num_rows=10, table_name=f"small_table_{i}") print("Creating table with long name") - name = "".join(random.choices(string.ascii_letters + string.digits, k=64)) + name = "".join(random.choices(string.ascii_letters + string.digits, k=60)) add_data(columns=columns, num_rows=10, table_name=name) diff --git a/superset/examples/birth_names.py b/superset/examples/birth_names.py index 8d7c02799dd57..de0018ce7cd95 100644 --- a/superset/examples/birth_names.py +++ b/superset/examples/birth_names.py @@ -847,7 +847,7 @@ def create_dashboard(slices: List[Slice]) -> Dashboard: # pylint: enable=line-too-long # dashboard v2 doesn't allow add markup slice dash.slices = [slc for slc in slices if slc.viz_type != "markup"] - update_slice_ids(pos, dash.slices) + update_slice_ids(pos) dash.dashboard_title = "USA Births Names" dash.position_json = json.dumps(pos, indent=4) dash.slug = "births" diff --git a/superset/examples/deck.py b/superset/examples/deck.py index ee01cd7b47511..f6c7a8c6996cf 100644 --- a/superset/examples/deck.py +++ b/superset/examples/deck.py @@ -33,6 +33,7 @@ "CHART-3afd9d70": { "meta": { "chartId": 66, + "sliceName": "Deck.gl Scatterplot", "width": 6, "height": 50 }, @@ -43,6 +44,7 @@ "CHART-2ee7fa5e": { "meta": { "chartId": 67, + "sliceName": "Deck.gl Screen grid", "width": 6, "height": 50 }, @@ -53,6 +55,7 @@ "CHART-201f7715": { "meta": { "chartId": 68, + "sliceName": "Deck.gl Hexagons", "width": 6, "height": 50 }, @@ -63,6 +66,7 @@ "CHART-d02f6c40": { "meta": { "chartId": 69, + "sliceName": "Deck.gl Grid", "width": 6, "height": 50 }, @@ -73,6 +77,7 @@ "CHART-2673431d": { "meta": { "chartId": 70, + "sliceName": "Deck.gl Polygons", "width": 6, "height": 50 }, @@ -83,6 +88,7 @@ "CHART-85265a60": { "meta": { "chartId": 71, + "sliceName": "Deck.gl Arcs", "width": 6, "height": 50 }, @@ -93,6 +99,7 @@ "CHART-2b87513c": { "meta": { "chartId": 72, + "sliceName": "Deck.gl Path", "width": 6, "height": 50 }, @@ -204,7 +211,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements print("Creating Scatterplot slice") slc = Slice( - slice_name="Scatterplot", + slice_name="Deck.gl Scatterplot", viz_type="deck_scatter", datasource_type="table", datasource_id=tbl.id, @@ -239,7 +246,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements } print("Creating Screen Grid slice") slc = Slice( - slice_name="Screen grid", + slice_name="Deck.gl Screen grid", viz_type="deck_screengrid", datasource_type="table", datasource_id=tbl.id, @@ -275,7 +282,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements } print("Creating Hex slice") slc = Slice( - slice_name="Hexagons", + slice_name="Deck.gl Hexagons", viz_type="deck_hex", datasource_type="table", datasource_id=tbl.id, @@ -312,7 +319,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements } print("Creating Grid slice") slc = Slice( - slice_name="Grid", + slice_name="Deck.gl Grid", viz_type="deck_grid", datasource_type="table", datasource_id=tbl.id, @@ -401,7 +408,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements print("Creating Polygon slice") slc = Slice( - slice_name="Polygons", + slice_name="Deck.gl Polygons", viz_type="deck_polygon", datasource_type="table", datasource_id=polygon_tbl.id, @@ -451,7 +458,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements print("Creating Arc slice") slc = Slice( - slice_name="Arcs", + slice_name="Deck.gl Arcs", viz_type="deck_arc", datasource_type="table", datasource_id=db.session.query(table) @@ -503,7 +510,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements print("Creating Path slice") slc = Slice( - slice_name="Path", + slice_name="Deck.gl Path", viz_type="deck_path", datasource_type="table", datasource_id=db.session.query(table) @@ -525,7 +532,7 @@ def load_deck_dash() -> None: # pylint: disable=too-many-statements dash.published = True js = POSITION_JSON pos = json.loads(js) - update_slice_ids(pos, slices) + slices = update_slice_ids(pos) dash.position_json = json.dumps(pos, indent=4) dash.dashboard_title = title dash.slug = slug diff --git a/superset/examples/helpers.py b/superset/examples/helpers.py index d8b2c59fb777d..9d17e73773299 100644 --- a/superset/examples/helpers.py +++ b/superset/examples/helpers.py @@ -39,17 +39,24 @@ def get_examples_folder() -> str: return os.path.join(app.config["BASE_DIR"], "examples") -def update_slice_ids(layout_dict: Dict[Any, Any], slices: List[Slice]) -> None: - charts = [ +def update_slice_ids(pos: Dict[Any, Any]) -> List[Slice]: + """Update slice ids in position_json and return the slices found.""" + slice_components = [ component - for component in layout_dict.values() - if isinstance(component, dict) and component["type"] == "CHART" + for component in pos.values() + if isinstance(component, dict) and component.get("type") == "CHART" ] - sorted_charts = sorted(charts, key=lambda k: k["meta"]["chartId"]) - for i, chart_component in enumerate(sorted_charts): - if i < len(slices): - chart_component["meta"]["chartId"] = int(slices[i].id) - chart_component["meta"]["uuid"] = str(slices[i].uuid) + slices = {} + for name in set(component["meta"]["sliceName"] for component in slice_components): + slc = db.session.query(Slice).filter_by(slice_name=name).first() + if slc: + slices[name] = slc + for component in slice_components: + slc = slices.get(component["meta"]["sliceName"]) + if slc: + component["meta"]["chartId"] = slc.id + component["meta"]["uuid"] = str(slc.uuid) + return list(slices.values()) def merge_slice(slc: Slice) -> None: diff --git a/superset/examples/misc_dashboard.py b/superset/examples/misc_dashboard.py index a8f282ee431cf..4146ea1bd38f0 100644 --- a/superset/examples/misc_dashboard.py +++ b/superset/examples/misc_dashboard.py @@ -19,9 +19,8 @@ from superset import db from superset.models.dashboard import Dashboard -from superset.models.slice import Slice -from .helpers import misc_dash_slices, update_slice_ids +from .helpers import update_slice_ids DASH_SLUG = "misc_charts" @@ -211,11 +210,7 @@ def load_misc_dashboard() -> None: """ ) pos = json.loads(js) - slices = ( - db.session.query(Slice).filter(Slice.slice_name.in_(misc_dash_slices)).all() - ) - slices = sorted(slices, key=lambda x: x.id) - update_slice_ids(pos, slices) + slices = update_slice_ids(pos) dash.dashboard_title = "Misc Charts" dash.position_json = json.dumps(pos, indent=4) dash.slug = DASH_SLUG diff --git a/superset/examples/tabbed_dashboard.py b/superset/examples/tabbed_dashboard.py index 1eaed48f25e70..7a167bc357d5f 100644 --- a/superset/examples/tabbed_dashboard.py +++ b/superset/examples/tabbed_dashboard.py @@ -20,7 +20,6 @@ from superset import db from superset.models.dashboard import Dashboard -from superset.models.slice import Slice from .helpers import update_slice_ids @@ -35,304 +34,492 @@ def load_tabbed_dashboard(_: bool = False) -> None: if not dash: dash = Dashboard() - # reuse charts in "World's Bank Data and create - # new dashboard with nested tabs - tabbed_dash_slices = set() - tabbed_dash_slices.add("Region Filter") - tabbed_dash_slices.add("Growth Rate") - tabbed_dash_slices.add("Treemap") - tabbed_dash_slices.add("Box plot") - js = textwrap.dedent( - """\ - { - "CHART-c0EjR-OZ0n": { - "children": [], - "id": "CHART-c0EjR-OZ0n", - "meta": { - "chartId": 870, - "height": 50, - "sliceName": "Box plot", - "width": 4 - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "ROW-7G2o5uDvfo" - ], - "type": "CHART" - }, - "CHART-dxV7Il74hH": { - "children": [], - "id": "CHART-dxV7Il74hH", - "meta": { - "chartId": 797, - "height": 50, - "sliceName": "Treemap", - "width": 4 - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-gcQJxApOZS", - "TABS-afnrUvdxYF", - "TAB-jNNd4WWar1", - "ROW-7ygtDczaQ" - ], - "type": "CHART" - }, - "CHART-jJ5Yj1Ptaz": { - "children": [], - "id": "CHART-jJ5Yj1Ptaz", - "meta": { - "chartId": 789, - "height": 50, - "sliceName": "World's Population", - "width": 4 - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj", - "TAB-z81Q87PD7", - "ROW-G73z9PIHn" - ], - "type": "CHART" - }, - "CHART-z4gmEuCqQ5": { - "children": [], - "id": "CHART-z4gmEuCqQ5", - "meta": { - "chartId": 788, - "height": 50, - "sliceName": "Region Filter", - "width": 4 - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj", - "TAB-EcNm_wh922", - "ROW-LCjsdSetJ" - ], - "type": "CHART" - }, - "DASHBOARD_VERSION_KEY": "v2", - "GRID_ID": { - "children": [], - "id": "GRID_ID", - "type": "GRID" - }, - "HEADER_ID": { - "id": "HEADER_ID", - "meta": { - "text": "Tabbed Dashboard" - }, - "type": "HEADER" - }, - "ROOT_ID": { - "children": [ - "TABS-lV0r00f4H1" - ], - "id": "ROOT_ID", - "type": "ROOT" - }, - "ROW-7G2o5uDvfo": { - "children": [ - "CHART-c0EjR-OZ0n" - ], - "id": "ROW-7G2o5uDvfo", - "meta": { - "background": "BACKGROUND_TRANSPARENT" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS" - ], - "type": "ROW" + """ +{ + "CHART-06Kg-rUggO": { + "children": [], + "id": "CHART-06Kg-rUggO", + "meta": { + "chartId": 617, + "height": 42, + "sliceName": "Number of Girls", + "width": 4 }, - "ROW-7ygtDczaQ": { - "children": [ - "CHART-dxV7Il74hH" - ], - "id": "ROW-7ygtDczaQ", - "meta": { - "background": "BACKGROUND_TRANSPARENT" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-gcQJxApOZS", - "TABS-afnrUvdxYF", - "TAB-jNNd4WWar1" - ], - "type": "ROW" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8", + "TAB-SDz1jDqYZ2", + "ROW-DnYkJgKQE" + ], + "type": "CHART" + }, + "CHART-E4rQMdzY9-": { + "children": [], + "id": "CHART-E4rQMdzY9-", + "meta": { + "chartId": 616, + "height": 41, + "sliceName": "Names Sorted by Num in California", + "width": 4 }, - "ROW-G73z9PIHn": { - "children": [ - "CHART-jJ5Yj1Ptaz" - ], - "id": "ROW-G73z9PIHn", - "meta": { - "background": "BACKGROUND_TRANSPARENT" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj", - "TAB-z81Q87PD7" - ], - "type": "ROW" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8", + "TAB-SDz1jDqYZ2", + "ROW-DnYkJgKQE" + ], + "type": "CHART" + }, + "CHART-WO52N6b5de": { + "children": [], + "id": "CHART-WO52N6b5de", + "meta": { + "chartId": 615, + "height": 41, + "sliceName": "Top 10 California Names Timeseries", + "width": 8 }, - "ROW-LCjsdSetJ": { - "children": [ - "CHART-z4gmEuCqQ5" - ], - "id": "ROW-LCjsdSetJ", - "meta": { - "background": "BACKGROUND_TRANSPARENT" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj", - "TAB-EcNm_wh922" - ], - "type": "ROW" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8", + "TAB-t54frVKlx", + "ROW-ghqEVzr2fA" + ], + "type": "CHART" + }, + "CHART-c0EjR-OZ0n": { + "children": [], + "id": "CHART-c0EjR-OZ0n", + "meta": { + "chartId": 598, + "height": 50, + "sliceName": "Treemap", + "width": 4 }, - "TAB-EcNm_wh922": { - "children": [ - "ROW-LCjsdSetJ" - ], - "id": "TAB-EcNm_wh922", - "meta": { - "text": "row tab 1" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj" - ], - "type": "TAB" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-RGd6kjW57J" + ], + "type": "CHART" + }, + "CHART-dxV7Il74hH": { + "children": [], + "id": "CHART-dxV7Il74hH", + "meta": { + "chartId": 597, + "height": 50, + "sliceName": "Box plot", + "width": 4 }, - "TAB-NF3dlrWGS": { - "children": [ - "ROW-7G2o5uDvfo", - "TABS-CSjo6VfNrj" - ], - "id": "TAB-NF3dlrWGS", - "meta": { - "text": "Tab A" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1" - ], - "type": "TAB" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-gcQJxApOZS", + "TABS-afnrUvdxYF", + "TAB-jNNd4WWar1", + "ROW-7ygtDczaQ" + ], + "type": "CHART" + }, + "CHART-jJ5Yj1Ptaz": { + "children": [], + "id": "CHART-jJ5Yj1Ptaz", + "meta": { + "chartId": 592, + "height": 29, + "sliceName": "Growth Rate", + "width": 5 }, - "TAB-gcQJxApOZS": { - "children": [ - "TABS-afnrUvdxYF" - ], - "id": "TAB-gcQJxApOZS", - "meta": { - "text": "Tab B" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1" - ], - "type": "TAB" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn" + ], + "type": "CHART" + }, + "CHART-z4gmEuCqQ5": { + "children": [], + "id": "CHART-z4gmEuCqQ5", + "meta": { + "chartId": 589, + "height": 50, + "sliceName": "Region Filter", + "width": 4 }, - "TAB-jNNd4WWar1": { - "children": [ - "ROW-7ygtDczaQ" - ], - "id": "TAB-jNNd4WWar1", - "meta": { - "text": "New Tab" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-gcQJxApOZS", - "TABS-afnrUvdxYF" - ], - "type": "TAB" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-EcNm_wh922", + "ROW-LCjsdSetJ" + ], + "type": "CHART" + }, + "COLUMN-RGd6kjW57J": { + "children": ["CHART-c0EjR-OZ0n"], + "id": "COLUMN-RGd6kjW57J", + "meta": { "background": "BACKGROUND_TRANSPARENT", "width": 4 }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N" + ], + "type": "COLUMN" + }, + "COLUMN-V6vsdWdOEJ": { + "children": ["TABS-urzRuDRusW"], + "id": "COLUMN-V6vsdWdOEJ", + "meta": { "background": "BACKGROUND_TRANSPARENT", "width": 7 }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn" + ], + "type": "COLUMN" + }, + "COLUMN-_o23occSTg": { + "children": ["TABS-CslNeIC6x8"], + "id": "COLUMN-_o23occSTg", + "meta": { "background": "BACKGROUND_TRANSPARENT", "width": 8 }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N" + ], + "type": "COLUMN" + }, + "DASHBOARD_VERSION_KEY": "v2", + "GRID_ID": { "children": [], "id": "GRID_ID", "type": "GRID" }, + "HEADER_ID": { + "id": "HEADER_ID", + "type": "HEADER", + "meta": { "text": "Tabbed Dashboard" } + }, + "ROOT_ID": { + "children": ["TABS-lV0r00f4H1"], + "id": "ROOT_ID", + "type": "ROOT" + }, + "ROW-7ygtDczaQ": { + "children": ["CHART-dxV7Il74hH"], + "id": "ROW-7ygtDczaQ", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-gcQJxApOZS", + "TABS-afnrUvdxYF", + "TAB-jNNd4WWar1" + ], + "type": "ROW" + }, + "ROW-DnYkJgKQE": { + "children": ["CHART-06Kg-rUggO", "CHART-E4rQMdzY9-"], + "id": "ROW-DnYkJgKQE", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8", + "TAB-SDz1jDqYZ2" + ], + "type": "ROW" + }, + "ROW-G73z9PIHn": { + "children": ["CHART-jJ5Yj1Ptaz", "COLUMN-V6vsdWdOEJ"], + "id": "ROW-G73z9PIHn", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7" + ], + "type": "ROW" + }, + "ROW-LCjsdSetJ": { + "children": ["CHART-z4gmEuCqQ5"], + "id": "ROW-LCjsdSetJ", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-EcNm_wh922" + ], + "type": "ROW" + }, + "ROW-ghqEVzr2fA": { + "children": ["CHART-WO52N6b5de"], + "id": "ROW-ghqEVzr2fA", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8", + "TAB-t54frVKlx" + ], + "type": "ROW" + }, + "ROW-kHj58UJg5N": { + "children": ["COLUMN-RGd6kjW57J", "COLUMN-_o23occSTg"], + "id": "ROW-kHj58UJg5N", + "meta": { "background": "BACKGROUND_TRANSPARENT" }, + "parents": ["ROOT_ID", "TABS-lV0r00f4H1", "TAB-NF3dlrWGS"], + "type": "ROW" + }, + "TAB-0yhA2SgdPg": { + "children": ["ROW-Gr9YPyQGwf"], + "id": "TAB-0yhA2SgdPg", + "meta": { + "defaultText": "Tab title", + "placeholder": "Tab title", + "text": "Level 2 nested tab 1" }, - "TAB-z81Q87PD7": { - "children": [ - "ROW-G73z9PIHn" - ], - "id": "TAB-z81Q87PD7", - "meta": { - "text": "row tab 2" - }, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS", - "TABS-CSjo6VfNrj" - ], - "type": "TAB" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn", + "COLUMN-V6vsdWdOEJ", + "TABS-urzRuDRusW" + ], + "type": "TAB" + }, + "TAB-3a1Gvm-Ef": { + "children": [], + "id": "TAB-3a1Gvm-Ef", + "meta": { + "defaultText": "Tab title", + "placeholder": "Tab title", + "text": "Level 2 nested tab 2" }, - "TABS-CSjo6VfNrj": { - "children": [ - "TAB-EcNm_wh922", - "TAB-z81Q87PD7" - ], - "id": "TABS-CSjo6VfNrj", - "meta": {}, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-NF3dlrWGS" - ], - "type": "TABS" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn", + "COLUMN-V6vsdWdOEJ", + "TABS-urzRuDRusW" + ], + "type": "TAB" + }, + "TAB-EcNm_wh922": { + "children": ["ROW-LCjsdSetJ"], + "id": "TAB-EcNm_wh922", + "meta": { "text": "row tab 1" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj" + ], + "type": "TAB" + }, + "TAB-NF3dlrWGS": { + "children": ["ROW-kHj58UJg5N", "TABS-CSjo6VfNrj"], + "id": "TAB-NF3dlrWGS", + "meta": { "text": "Tab A" }, + "parents": ["ROOT_ID", "TABS-lV0r00f4H1"], + "type": "TAB" + }, + "TAB-SDz1jDqYZ2": { + "children": ["ROW-DnYkJgKQE"], + "id": "TAB-SDz1jDqYZ2", + "meta": { + "defaultText": "Tab title", + "placeholder": "Tab title", + "text": "Nested tab 1" }, - "TABS-afnrUvdxYF": { - "children": [ - "TAB-jNNd4WWar1" - ], - "id": "TABS-afnrUvdxYF", - "meta": {}, - "parents": [ - "ROOT_ID", - "TABS-lV0r00f4H1", - "TAB-gcQJxApOZS" - ], - "type": "TABS" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8" + ], + "type": "TAB" + }, + "TAB-gcQJxApOZS": { + "children": ["TABS-afnrUvdxYF"], + "id": "TAB-gcQJxApOZS", + "meta": { "text": "Tab B" }, + "parents": ["ROOT_ID", "TABS-lV0r00f4H1"], + "type": "TAB" + }, + "TAB-jNNd4WWar1": { + "children": ["ROW-7ygtDczaQ"], + "id": "TAB-jNNd4WWar1", + "meta": { "text": "New Tab" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-gcQJxApOZS", + "TABS-afnrUvdxYF" + ], + "type": "TAB" + }, + "TAB-t54frVKlx": { + "children": ["ROW-ghqEVzr2fA"], + "id": "TAB-t54frVKlx", + "meta": { + "defaultText": "Tab title", + "placeholder": "Tab title", + "text": "Nested tab 2" }, - "TABS-lV0r00f4H1": { - "children": [ - "TAB-NF3dlrWGS", - "TAB-gcQJxApOZS" - ], - "id": "TABS-lV0r00f4H1", - "meta": {}, - "parents": [ - "ROOT_ID" - ], - "type": "TABS" + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg", + "TABS-CslNeIC6x8" + ], + "type": "TAB" + }, + "TAB-z81Q87PD7": { + "children": ["ROW-G73z9PIHn"], + "id": "TAB-z81Q87PD7", + "meta": { "text": "row tab 2" }, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj" + ], + "type": "TAB" + }, + "TABS-CSjo6VfNrj": { + "children": ["TAB-EcNm_wh922", "TAB-z81Q87PD7"], + "id": "TABS-CSjo6VfNrj", + "meta": {}, + "parents": ["ROOT_ID", "TABS-lV0r00f4H1", "TAB-NF3dlrWGS"], + "type": "TABS" + }, + "TABS-CslNeIC6x8": { + "children": ["TAB-SDz1jDqYZ2", "TAB-t54frVKlx"], + "id": "TABS-CslNeIC6x8", + "meta": {}, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "ROW-kHj58UJg5N", + "COLUMN-_o23occSTg" + ], + "type": "TABS" + }, + "TABS-afnrUvdxYF": { + "children": ["TAB-jNNd4WWar1"], + "id": "TABS-afnrUvdxYF", + "meta": {}, + "parents": ["ROOT_ID", "TABS-lV0r00f4H1", "TAB-gcQJxApOZS"], + "type": "TABS" + }, + "TABS-lV0r00f4H1": { + "children": ["TAB-NF3dlrWGS", "TAB-gcQJxApOZS"], + "id": "TABS-lV0r00f4H1", + "meta": {}, + "parents": ["ROOT_ID"], + "type": "TABS" + }, + "TABS-urzRuDRusW": { + "children": ["TAB-0yhA2SgdPg", "TAB-3a1Gvm-Ef"], + "id": "TABS-urzRuDRusW", + "meta": {}, + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn", + "COLUMN-V6vsdWdOEJ" + ], + "type": "TABS" + }, + "CHART-p4_VUp8w3w": { + "type": "CHART", + "id": "CHART-p4_VUp8w3w", + "children": [], + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn", + "COLUMN-V6vsdWdOEJ", + "TABS-urzRuDRusW", + "TAB-0yhA2SgdPg", + "ROW-Gr9YPyQGwf" + ], + "meta": { + "width": 4, + "height": 20, + "chartId": 614, + "sliceName": "Number of California Births" } + }, + "ROW-Gr9YPyQGwf": { + "type": "ROW", + "id": "ROW-Gr9YPyQGwf", + "children": ["CHART-p4_VUp8w3w"], + "parents": [ + "ROOT_ID", + "TABS-lV0r00f4H1", + "TAB-NF3dlrWGS", + "TABS-CSjo6VfNrj", + "TAB-z81Q87PD7", + "ROW-G73z9PIHn", + "COLUMN-V6vsdWdOEJ", + "TABS-urzRuDRusW", + "TAB-0yhA2SgdPg" + ], + "meta": { "background": "BACKGROUND_TRANSPARENT" } } - """ +}""" ) pos = json.loads(js) - slices = [ - db.session.query(Slice).filter_by(slice_name=name).first() - for name in tabbed_dash_slices - ] - - slices = sorted(slices, key=lambda x: x.id) - update_slice_ids(pos, slices) + slices = update_slice_ids(pos) dash.position_json = json.dumps(pos, indent=4) dash.slices = slices dash.dashboard_title = "Tabbed Dashboard" diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py index 015a8f9297978..421818724f83c 100644 --- a/superset/examples/world_bank.py +++ b/superset/examples/world_bank.py @@ -128,13 +128,12 @@ def load_world_bank_health_n_pop( # pylint: disable=too-many-locals, too-many-s dash = Dashboard() dash.published = True pos = dashboard_positions - update_slice_ids(pos, slices) + slices = update_slice_ids(pos) dash.dashboard_title = dash_name dash.position_json = json.dumps(pos, indent=4) dash.slug = slug - - dash.slices = slices[:-1] + dash.slices = slices db.session.merge(dash) db.session.commit() @@ -460,12 +459,6 @@ def create_slices(tbl: BaseDatasource) -> List[Slice]: "meta": {"chartId": 49, "height": 50, "sliceName": "Treemap", "width": 8}, "type": "CHART", }, - "CHART-3nc0d8sk": { - "children": [], - "id": "CHART-3nc0d8sk", - "meta": {"chartId": 50, "height": 50, "sliceName": "Treemap", "width": 8}, - "type": "CHART", - }, "COLUMN-071bbbad": { "children": ["ROW-1e064e3c", "ROW-afdefba9"], "id": "COLUMN-071bbbad", diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 1364e53d25775..f2d53e1ff5e6a 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -145,7 +145,9 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin): certification_details = Column(Text) json_metadata = Column(Text) slug = Column(String(255), unique=True) - slices = relationship(Slice, secondary=dashboard_slices, backref="dashboards") + slices: List[Slice] = relationship( + Slice, secondary=dashboard_slices, backref="dashboards" + ) owners = relationship(security_manager.user_model, secondary=dashboard_user) published = Column(Boolean, default=False) is_managed_externally = Column(Boolean, nullable=False, default=False) @@ -218,7 +220,7 @@ def filter_sets_lst(self) -> Dict[int, FilterSet]: } @property - def charts(self) -> List[BaseDatasource]: + def charts(self) -> List[str]: return [slc.chart for slc in self.slices] @property diff --git a/tests/integration_tests/dashboards/commands_tests.py b/tests/integration_tests/dashboards/commands_tests.py index ae18c741583e7..e3e6971155b9f 100644 --- a/tests/integration_tests/dashboards/commands_tests.py +++ b/tests/integration_tests/dashboards/commands_tests.py @@ -168,12 +168,6 @@ def test_export_dashboard_command(self, mock_g1, mock_g2): "meta": {"height": 50, "sliceName": "Treemap", "width": 8}, "type": "CHART", }, - "CHART-3nc0d8sk": { - "children": [], - "id": "CHART-3nc0d8sk", - "meta": {"height": 50, "sliceName": "Treemap", "width": 8}, - "type": "CHART", - }, "COLUMN-071bbbad": { "children": ["ROW-1e064e3c", "ROW-afdefba9"], "id": "COLUMN-071bbbad", diff --git a/tests/integration_tests/dashboards/dao_tests.py b/tests/integration_tests/dashboards/dao_tests.py index 4a02cd7107acd..e9d73764955f0 100644 --- a/tests/integration_tests/dashboards/dao_tests.py +++ b/tests/integration_tests/dashboards/dao_tests.py @@ -35,15 +35,17 @@ class TestDashboardDAO(SupersetTestCase): @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") def test_set_dash_metadata(self): - dash = db.session.query(Dashboard).filter_by(slug="world_health").first() + dash: Dashboard = ( + db.session.query(Dashboard).filter_by(slug="world_health").first() + ) data = dash.data positions = data["position_json"] data.update({"positions": positions}) original_data = copy.deepcopy(data) # add filter scopes - filter_slice = dash.slices[0] - immune_slices = dash.slices[2:] + filter_slice = next(slc for slc in dash.slices if slc.viz_type == "filter_box") + immune_slices = [slc for slc in dash.slices if slc != filter_slice] filter_scopes = { str(filter_slice.id): { "region": { @@ -59,14 +61,15 @@ def test_set_dash_metadata(self): # remove a slice and change slice ids (as copy slices) removed_slice = immune_slices.pop() - removed_component = [ + removed_components = [ key for (key, value) in positions.items() if isinstance(value, dict) and value.get("type") == "CHART" and value["meta"]["chartId"] == removed_slice.id ] - positions.pop(removed_component[0], None) + for component_id in removed_components: + del positions[component_id] data.update({"positions": positions}) DashboardDAO.set_dash_metadata(dash, data) diff --git a/tests/integration_tests/fixtures/world_bank_dashboard.py b/tests/integration_tests/fixtures/world_bank_dashboard.py index e767036b7d857..2c6fb2c3e26e4 100644 --- a/tests/integration_tests/fixtures/world_bank_dashboard.py +++ b/tests/integration_tests/fixtures/world_bank_dashboard.py @@ -87,7 +87,7 @@ def create_dashboard_for_loaded_data(): with app.app_context(): table = create_table_metadata(WB_HEALTH_POPULATION, get_example_database()) slices = _create_world_bank_slices(table) - dash = _create_world_bank_dashboard(table, slices) + dash = _create_world_bank_dashboard(table) slices_ids_to_delete = [slice.id for slice in slices] dash_id_to_delete = dash.id return dash_id_to_delete, slices_ids_to_delete @@ -110,12 +110,12 @@ def _commit_slices(slices: List[Slice]): db.session.commit() -def _create_world_bank_dashboard(table: SqlaTable, slices: List[Slice]) -> Dashboard: +def _create_world_bank_dashboard(table: SqlaTable) -> Dashboard: from superset.examples.helpers import update_slice_ids from superset.examples.world_bank import dashboard_positions pos = dashboard_positions - update_slice_ids(pos, slices) + slices = update_slice_ids(pos) table.fetch_metadata()