mirror of
https://github.com/apache/superset.git
synced 2026-08-28 11:01:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
799aab886c | ||
|
|
6765e64906 | ||
|
|
d0aeaa0b06 | ||
|
|
d5a8c9dfbc | ||
|
|
5ae942a17f | ||
|
|
1504773f12 | ||
|
|
8e46f5edcf |
@@ -47,6 +47,7 @@ type LayoutElementLabel =
|
||||
export class DashboardPage {
|
||||
private readonly page: Page;
|
||||
private readonly filterBar: DashboardFilterBar;
|
||||
private readonly dashboardTabs: Tabs;
|
||||
|
||||
private static readonly SELECTORS = {
|
||||
DASHBOARD_HEADER: '[data-test="dashboard-header-container"]',
|
||||
@@ -72,11 +73,19 @@ export class DashboardPage {
|
||||
ACE_CONTENT: '.ace_content',
|
||||
ACE_TEXT_INPUT: '.ace_text-input',
|
||||
RESIZE_HANDLE_BOTTOM: '.resizable-container-handle--bottom',
|
||||
DASHBOARD_TABS: '[data-test="dashboard-component-tabs"]',
|
||||
} as const;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.filterBar = new DashboardFilterBar(page);
|
||||
this.dashboardTabs = new Tabs(
|
||||
page,
|
||||
page
|
||||
.locator(DashboardPage.SELECTORS.DASHBOARD_TABS)
|
||||
.first()
|
||||
.locator(':scope > [data-test="nav-list"]'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +225,16 @@ export class DashboardPage {
|
||||
return this.filterBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to a top-level dashboard tab and waits for it to become active.
|
||||
*/
|
||||
async switchDashboardTab(tabName: string): Promise<void> {
|
||||
await this.dashboardTabs.clickTab(tabName);
|
||||
await expect
|
||||
.poll(() => this.dashboardTabs.getActiveTabName())
|
||||
.toBe(tabName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dashboard header actions menu (three-dot menu)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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 getEmptyLayout from '../../../src/dashboard/util/getEmptyLayout';
|
||||
import {
|
||||
BACKGROUND_TRANSPARENT,
|
||||
DASHBOARD_GRID_ID,
|
||||
DASHBOARD_ROOT_ID,
|
||||
} from '../../../src/dashboard/util/constants';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
} from '../../../src/dashboard/util/componentTypes';
|
||||
import { testWithAssets, expect } from '../../helpers/fixtures';
|
||||
import type {
|
||||
DashboardLayoutChart,
|
||||
DashboardPositionJson,
|
||||
} from '../../helpers/api/dashboard';
|
||||
import { TIMEOUT } from '../../utils/constants';
|
||||
import { DashboardPage } from '../../pages/DashboardPage';
|
||||
import { createDashboardWithCharts } from './dashboard-test-helpers';
|
||||
|
||||
const DATASET_NAME = 'birth_names';
|
||||
const WIDE_VIEWPORT = { width: 1400, height: 900 };
|
||||
const NARROW_VIEWPORT = { width: 700, height: 900 };
|
||||
const TABS_ID = 'TABS-TOP';
|
||||
const FIRST_TAB_ID = 'TAB-A';
|
||||
const SECOND_TAB_ID = 'TAB-B';
|
||||
const ROW_ID = 'ROW-A';
|
||||
|
||||
function buildTabbedDashboardLayout(
|
||||
charts: readonly DashboardLayoutChart[],
|
||||
): DashboardPositionJson {
|
||||
const [treemap] = charts;
|
||||
if (!treemap) {
|
||||
throw new Error('Tabbed dashboard layout requires a chart');
|
||||
}
|
||||
|
||||
const emptyLayout = getEmptyLayout();
|
||||
const chartKey = `CHART-${treemap.id}`;
|
||||
|
||||
return {
|
||||
...emptyLayout,
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
...emptyLayout[DASHBOARD_GRID_ID],
|
||||
children: [TABS_ID],
|
||||
},
|
||||
[TABS_ID]: {
|
||||
type: TABS_TYPE,
|
||||
id: TABS_ID,
|
||||
children: [FIRST_TAB_ID, SECOND_TAB_ID],
|
||||
parents: [DASHBOARD_ROOT_ID, DASHBOARD_GRID_ID],
|
||||
meta: {},
|
||||
},
|
||||
[FIRST_TAB_ID]: {
|
||||
type: TAB_TYPE,
|
||||
id: FIRST_TAB_ID,
|
||||
children: [ROW_ID],
|
||||
parents: [DASHBOARD_ROOT_ID, DASHBOARD_GRID_ID, TABS_ID],
|
||||
meta: {
|
||||
text: 'Tab A',
|
||||
defaultText: 'Tab title',
|
||||
placeholder: 'Tab title',
|
||||
},
|
||||
},
|
||||
[SECOND_TAB_ID]: {
|
||||
type: TAB_TYPE,
|
||||
id: SECOND_TAB_ID,
|
||||
children: [],
|
||||
parents: [DASHBOARD_ROOT_ID, DASHBOARD_GRID_ID, TABS_ID],
|
||||
meta: {
|
||||
text: 'Tab B',
|
||||
defaultText: 'Tab title',
|
||||
placeholder: 'Tab title',
|
||||
},
|
||||
},
|
||||
[ROW_ID]: {
|
||||
type: ROW_TYPE,
|
||||
id: ROW_ID,
|
||||
children: [chartKey],
|
||||
parents: [DASHBOARD_ROOT_ID, DASHBOARD_GRID_ID, TABS_ID, FIRST_TAB_ID],
|
||||
meta: { background: BACKGROUND_TRANSPARENT },
|
||||
},
|
||||
[chartKey]: {
|
||||
type: CHART_TYPE,
|
||||
id: chartKey,
|
||||
children: [],
|
||||
parents: [
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_GRID_ID,
|
||||
TABS_ID,
|
||||
FIRST_TAB_ID,
|
||||
ROW_ID,
|
||||
],
|
||||
meta: {
|
||||
chartId: treemap.id,
|
||||
width: 12,
|
||||
height: 50,
|
||||
sliceName: treemap.sliceName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
testWithAssets(
|
||||
'chart in a hidden tab refits its container after the tab is revealed at a new width',
|
||||
async ({ page, testAssets }, testInfo) => {
|
||||
testWithAssets.setTimeout(TIMEOUT.SLOW_TEST);
|
||||
|
||||
const { dashboardId, charts } = await createDashboardWithCharts(
|
||||
page,
|
||||
testAssets,
|
||||
testInfo,
|
||||
{
|
||||
datasetName: DATASET_NAME,
|
||||
chartNamePrefix: 'tabs',
|
||||
dashboardTitlePrefix: 'tabs_resize',
|
||||
chartSpecs: [
|
||||
{
|
||||
viz_type: 'treemap_v2',
|
||||
params: {
|
||||
metric: 'count',
|
||||
groupby: ['gender'],
|
||||
row_limit: 100,
|
||||
},
|
||||
},
|
||||
],
|
||||
buildLayout: buildTabbedDashboardLayout,
|
||||
},
|
||||
);
|
||||
const [treemap] = charts;
|
||||
if (!treemap) {
|
||||
throw new Error('Dashboard setup did not create the treemap');
|
||||
}
|
||||
|
||||
await page.setViewportSize(WIDE_VIEWPORT);
|
||||
|
||||
const dashboard = new DashboardPage(page);
|
||||
await dashboard.gotoById(dashboardId);
|
||||
await dashboard.waitForLoad();
|
||||
|
||||
const treemapContainer = dashboard
|
||||
.getChart(treemap.id)
|
||||
.locator('[data-test="chart-container"]');
|
||||
await treemapContainer.waitFor({
|
||||
state: 'visible',
|
||||
timeout: TIMEOUT.API_RESPONSE,
|
||||
});
|
||||
await dashboard.waitForChartsToLoad();
|
||||
|
||||
const echartsHost = treemapContainer.locator('.echarts-host');
|
||||
const widthAtWide = await echartsHost.evaluate(
|
||||
(element: HTMLElement) => element.offsetWidth,
|
||||
);
|
||||
|
||||
await dashboard.switchDashboardTab('Tab B');
|
||||
await page.setViewportSize(NARROW_VIEWPORT);
|
||||
await dashboard.switchDashboardTab('Tab A');
|
||||
|
||||
await treemapContainer.waitFor({
|
||||
state: 'visible',
|
||||
timeout: TIMEOUT.API_RESPONSE,
|
||||
});
|
||||
await dashboard.waitForChartsToLoad();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
echartsHost.evaluate((element: HTMLElement) => element.offsetWidth),
|
||||
{
|
||||
timeout: TIMEOUT.API_RESPONSE,
|
||||
message: 'treemap should resize after the hidden tab is revealed',
|
||||
},
|
||||
)
|
||||
.toBeLessThan(widthAtWide);
|
||||
|
||||
const { offsetWidth, scrollWidth } = await echartsHost.evaluate(
|
||||
(element: HTMLElement) => ({
|
||||
offsetWidth: element.offsetWidth,
|
||||
scrollWidth: element.scrollWidth,
|
||||
}),
|
||||
);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(offsetWidth);
|
||||
},
|
||||
);
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
apiPostDashboard,
|
||||
buildSingleRowDashboardLayout,
|
||||
type DashboardLayoutChart,
|
||||
type DashboardPositionJson,
|
||||
} from '../../helpers/api/dashboard';
|
||||
import { getDatasetByName } from '../../helpers/api/dataset';
|
||||
import { extractIdFromResponse } from '../../helpers/api/assertions';
|
||||
@@ -236,14 +237,17 @@ interface CreateDashboardWithChartsOptions {
|
||||
/** Dashboard title prefix: `${dashboardTitlePrefix}_${suffix}`. */
|
||||
dashboardTitlePrefix: string;
|
||||
chartSpecs: DashboardChartSpec[];
|
||||
/** Custom dashboard layout; defaults to placing every chart in one row. */
|
||||
buildLayout?: (
|
||||
charts: readonly DashboardLayoutChart[],
|
||||
) => DashboardPositionJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a published dashboard via the API: creates each chart, lays them out in
|
||||
* a single row, and associates them so they render. Every created chart and the
|
||||
* dashboard are registered for fixture cleanup. Charts are returned in the same
|
||||
* order as `chartSpecs`, so callers can pair them back to per-spec metadata by
|
||||
* index.
|
||||
* Builds a published dashboard via the API: creates each chart, lays them out,
|
||||
* and associates them so they render. Every created chart and the dashboard are
|
||||
* registered for fixture cleanup. Charts are returned in the same order as
|
||||
* `chartSpecs`, so callers can pair them back to per-spec metadata by index.
|
||||
*/
|
||||
export async function createDashboardWithCharts(
|
||||
page: Page,
|
||||
@@ -282,8 +286,9 @@ export async function createDashboardWithCharts(
|
||||
charts.push({ id: chartId, sliceName });
|
||||
}
|
||||
|
||||
// Lay all charts out in a single row.
|
||||
const positionJson = buildSingleRowDashboardLayout(charts);
|
||||
const positionJson = options.buildLayout
|
||||
? options.buildLayout(charts)
|
||||
: buildSingleRowDashboardLayout(charts);
|
||||
const dashResp = await apiPostDashboard(page, {
|
||||
dashboard_title: `${options.dashboardTitlePrefix}_${uniqueSuffix}`,
|
||||
published: true,
|
||||
|
||||
Reference in New Issue
Block a user