chore: E2E tests for the Drill to detail modal (#21187)
* Add example ECharts Dashboard * [WIP] E2E test * Add echarts interactions * Lint * DRY
This commit is contained in:
parent
ccb293a083
commit
f017f98b88
|
|
@ -23,6 +23,7 @@ export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
|
|||
export const USA_BIRTH_NAMES_DASHBOARD = '/superset/dashboard/births/';
|
||||
export const testDashboard = '/superset/dashboard/538/';
|
||||
export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/';
|
||||
export const ECHARTS_DASHBOARD = '/superset/dashboard/echarts_dash/';
|
||||
|
||||
export const testItems = {
|
||||
dashboard: 'Cypress test Dashboard',
|
||||
|
|
@ -73,6 +74,14 @@ export const WORLD_HEALTH_CHARTS = [
|
|||
{ name: 'Box plot', viz: 'box_plot' },
|
||||
] as const;
|
||||
|
||||
export const ECHARTS_CHARTS = [
|
||||
{ name: 'Number of Girls', viz: 'big_number_total' },
|
||||
{ name: 'Participants', viz: 'big_number' },
|
||||
{ name: 'Box plot', viz: 'box_plot' },
|
||||
{ name: 'Genders', viz: 'pie' },
|
||||
{ name: 'Energy Force Layout', viz: 'graph_chart' },
|
||||
] as const;
|
||||
|
||||
/** Used to specify charts expected by the test suite */
|
||||
export interface ChartSpec {
|
||||
name: string;
|
||||
|
|
@ -81,7 +90,7 @@ export interface ChartSpec {
|
|||
|
||||
export function getChartGridComponent({ name, viz }: ChartSpec) {
|
||||
return cy
|
||||
.get(`[data-test="chart-grid-component"][data-test-chart-name="${name}"]`)
|
||||
.get(`[data-test-chart-name="${name}"]`)
|
||||
.should('have.attr', 'data-test-viz-type', viz);
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +101,7 @@ export function waitForChartLoad(chart: ChartSpec) {
|
|||
return (
|
||||
cy
|
||||
// this id only becomes visible when the chart is loaded
|
||||
.get(`[data-test="chart-grid-component"] #chart-id-${chartId}`, {
|
||||
.get(`#chart-id-${chartId}`, {
|
||||
timeout: 30000,
|
||||
})
|
||||
.should('be.visible')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* 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 {
|
||||
waitForChartLoad,
|
||||
ECHARTS_CHARTS,
|
||||
ECHARTS_DASHBOARD,
|
||||
} from './dashboard.helper';
|
||||
|
||||
function interceptSamples() {
|
||||
cy.intercept(`/datasource/samples*`).as('samples');
|
||||
}
|
||||
|
||||
function openModalFromMenu(chartType: string) {
|
||||
interceptSamples();
|
||||
|
||||
cy.get(
|
||||
`[data-test-viz-type='${chartType}'] [aria-label='More Options']`,
|
||||
).click();
|
||||
cy.get('.ant-dropdown')
|
||||
.not('.ant-dropdown-hidden')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.eq(5)
|
||||
.should('contain', 'Drill to detail')
|
||||
.click();
|
||||
cy.wait('@samples');
|
||||
}
|
||||
|
||||
function openModalFromChartContext(targetMenuItem: string) {
|
||||
interceptSamples();
|
||||
|
||||
cy.get('.ant-dropdown')
|
||||
.not('.ant-dropdown-hidden')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.should('contain', targetMenuItem)
|
||||
.click();
|
||||
cy.wait('@samples');
|
||||
}
|
||||
|
||||
describe('Drill to detail modal', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit(ECHARTS_DASHBOARD);
|
||||
ECHARTS_CHARTS.forEach(waitForChartLoad);
|
||||
});
|
||||
|
||||
it('opens the modal from the context menu', () => {
|
||||
openModalFromMenu('big_number_total');
|
||||
|
||||
cy.get("[role='dialog'] .draggable-trigger").should(
|
||||
'contain',
|
||||
'Drill to detail: Number of Girls',
|
||||
);
|
||||
});
|
||||
|
||||
it('refreshes the data', () => {
|
||||
openModalFromMenu('big_number_total');
|
||||
// move to the last page
|
||||
cy.get(".pagination-container [role='navigation'] [role='button']")
|
||||
.eq(7)
|
||||
.click();
|
||||
cy.wait('@samples');
|
||||
// reload
|
||||
cy.get("[aria-label='reload']").click();
|
||||
cy.wait('@samples');
|
||||
// make sure it started back from first page
|
||||
cy.get(".pagination-container [role='navigation'] li.active").should(
|
||||
'contain',
|
||||
'1',
|
||||
);
|
||||
});
|
||||
|
||||
it('paginates', () => {
|
||||
openModalFromMenu('big_number_total');
|
||||
// checking the data
|
||||
cy.get("[data-test='row-count-label']").should('contain', '36.4k rows');
|
||||
cy.get("[role='rowgroup'] [role='row']")
|
||||
.should('have.length', 50)
|
||||
.then($rows => {
|
||||
expect($rows).to.contain('Amy');
|
||||
});
|
||||
// checking the paginated data
|
||||
cy.get(".pagination-container [role='navigation'] [role='button']")
|
||||
.should('have.length', 9)
|
||||
.then($pages => {
|
||||
expect($pages).to.contain('1');
|
||||
expect($pages).to.contain('729');
|
||||
});
|
||||
cy.get(".pagination-container [role='navigation'] [role='button']")
|
||||
.eq(7)
|
||||
.click();
|
||||
cy.wait('@samples');
|
||||
cy.get("[role='rowgroup'] [role='row']")
|
||||
.should('have.length', 46)
|
||||
.then($rows => {
|
||||
expect($rows).to.contain('Victoria');
|
||||
});
|
||||
});
|
||||
|
||||
it('clears filters', () => {
|
||||
interceptSamples();
|
||||
|
||||
// opens the modal by clicking on the box on the chart
|
||||
cy.get("[data-test-viz-type='box_plot'] canvas").then($canvas => {
|
||||
const canvasWidth = $canvas.width() || 0;
|
||||
const canvasHeight = $canvas.height() || 0;
|
||||
const canvasCenterX = canvasWidth / 6;
|
||||
const canvasCenterY = canvasHeight / 6;
|
||||
|
||||
cy.wrap($canvas)
|
||||
.scrollIntoView()
|
||||
.rightclick(canvasCenterX, canvasCenterY, { force: true });
|
||||
|
||||
openModalFromChartContext('Drill to detail by East Asia & Pacific');
|
||||
|
||||
// checking the filter
|
||||
cy.get("[data-test='filter-val']").should(
|
||||
'contain',
|
||||
'East Asia & Pacific',
|
||||
);
|
||||
cy.get("[data-test='row-count-label']").should('contain', '1.98k rows');
|
||||
cy.get(".pagination-container [role='navigation'] [role='button']")
|
||||
.should('have.length', 9)
|
||||
.then($pages => {
|
||||
expect($pages).to.contain('1');
|
||||
expect($pages).to.contain('40');
|
||||
});
|
||||
|
||||
// close the filter and test that data was reloaded
|
||||
cy.get("[data-test='filter-col']").find("[aria-label='close']").click();
|
||||
cy.wait('@samples');
|
||||
cy.get("[data-test='row-count-label']").should('contain', '11.8k rows');
|
||||
cy.get(".pagination-container [role='navigation'] li.active").should(
|
||||
'contain',
|
||||
'1',
|
||||
);
|
||||
cy.get(".pagination-container [role='navigation'] [role='button']")
|
||||
.should('have.length', 9)
|
||||
.then($pages => {
|
||||
expect($pages).to.contain('1');
|
||||
expect($pages).to.contain('236');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Time-series Bar Chart V2', () => {
|
||||
it('opens the modal with the correct filters', () => {
|
||||
interceptSamples();
|
||||
|
||||
cy.get("[data-test-viz-type='echarts_timeseries_bar'] canvas").then(
|
||||
$canvas => {
|
||||
cy.wrap($canvas)
|
||||
.scrollIntoView()
|
||||
.rightclick(70, 100, { force: true });
|
||||
cy.get('.ant-dropdown')
|
||||
.not('.ant-dropdown-hidden')
|
||||
.find("[role='menu'] [role='menuitem']")
|
||||
.should('have.length', 3)
|
||||
.then($menuitems => {
|
||||
expect($menuitems).to.contain('Drill to detail by 1965');
|
||||
expect($menuitems).to.contain('Drill to detail by boy');
|
||||
expect($menuitems).to.contain('Drill to detail by all');
|
||||
})
|
||||
.eq(2)
|
||||
.click();
|
||||
cy.wait('@samples');
|
||||
|
||||
cy.get("[data-test='filter-val']").then($filters => {
|
||||
expect($filters).to.contain('1965');
|
||||
expect($filters).to.contain('boy');
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Box plot', () => {
|
||||
it('opens the modal with the correct filters', () => {
|
||||
interceptSamples();
|
||||
|
||||
// opens the modal by clicking on the box on the chart
|
||||
cy.get("[data-test-viz-type='box_plot'] canvas").then($canvas => {
|
||||
const canvasWidth = $canvas.width() || 0;
|
||||
const canvasHeight = $canvas.height() || 0;
|
||||
const canvasCenterX = canvasWidth / 6;
|
||||
const canvasCenterY = canvasHeight / 6;
|
||||
|
||||
cy.wrap($canvas)
|
||||
.scrollIntoView()
|
||||
.rightclick(canvasCenterX, canvasCenterY, { force: true });
|
||||
|
||||
openModalFromChartContext('Drill to detail by East Asia & Pacific');
|
||||
|
||||
// checking the filter
|
||||
cy.get("[data-test='filter-val']").should(
|
||||
'contain',
|
||||
'East Asia & Pacific',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pie', () => {
|
||||
it('opens the modal with the correct filters', () => {
|
||||
interceptSamples();
|
||||
|
||||
// opens the modal by clicking on the slice of the Pie chart
|
||||
cy.get("[data-test-viz-type='pie'] canvas").then($canvas => {
|
||||
const canvasWidth = $canvas.width() || 0;
|
||||
const canvasHeight = $canvas.height() || 0;
|
||||
const canvasCenterX = canvasWidth / 2;
|
||||
const canvasCenterY = canvasHeight / 2;
|
||||
|
||||
cy.wrap($canvas)
|
||||
.scrollIntoView()
|
||||
.rightclick(canvasCenterX, canvasCenterY, { force: true });
|
||||
|
||||
openModalFromChartContext('Drill to detail by boy');
|
||||
|
||||
// checking the filtered and paginated data
|
||||
cy.get("[data-test='filter-val']").should('contain', 'boy');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Big number total', () => {
|
||||
it('opens the modal with no filters', () => {
|
||||
interceptSamples();
|
||||
|
||||
// opens the modal by clicking on the number on the chart
|
||||
cy.get(
|
||||
"[data-test-viz-type='big_number_total'] .header-line",
|
||||
).rightclick();
|
||||
|
||||
openModalFromChartContext('Drill to detail');
|
||||
|
||||
cy.get("[data-test='filter-val']").should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Big number with trendline', () => {
|
||||
it('opens the modal with the correct data', () => {
|
||||
interceptSamples();
|
||||
|
||||
// opens the modal by clicking on the number
|
||||
cy.get("[data-test-viz-type='big_number'] .header-line").rightclick();
|
||||
|
||||
openModalFromChartContext('Drill to detail');
|
||||
|
||||
cy.get("[data-test='filter-val']").should('not.exist');
|
||||
|
||||
// TODO: test clicking on a trendline
|
||||
// Cypress is refusing to rightclick on the dot
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -195,6 +195,7 @@ export default function transformProps(
|
|||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
showSymbol: false,
|
||||
color: mainColor,
|
||||
areaStyle: {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export default function TableControls({
|
|||
margin-bottom: ${theme.gridUnit * 4}px;
|
||||
line-height: 1.2;
|
||||
`}
|
||||
data-test="filter-col"
|
||||
>
|
||||
<span
|
||||
css={css`
|
||||
|
|
@ -113,7 +114,7 @@ export default function TableControls({
|
|||
>
|
||||
{colName}
|
||||
</span>
|
||||
<strong>{val}</strong>
|
||||
<strong data-test="filter-val">{val}</strong>
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,10 +35,14 @@ export default function RowCountLabel(props: RowCountLabelProps) {
|
|||
limitReached || (rowcount === 0 && !loading) ? 'danger' : 'default';
|
||||
const formattedRowCount = getNumberFormatter()(rowcount);
|
||||
const label = (
|
||||
<Label type={type} data-test="row-count-label">
|
||||
{loading
|
||||
? t('Loading...')
|
||||
: tn('%s row', '%s rows', rowcount, formattedRowCount)}
|
||||
<Label type={type}>
|
||||
{loading ? (
|
||||
t('Loading...')
|
||||
) : (
|
||||
<span data-test="row-count-label">
|
||||
{tn('%s row', '%s rows', rowcount, formattedRowCount)}
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
);
|
||||
return limitReached ? (
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ def load_examples_run(
|
|||
if load_test_data:
|
||||
print("Loading [Tabbed dashboard]")
|
||||
examples.load_tabbed_dashboard(only_metadata)
|
||||
|
||||
print("Loading [ECharts Dashboard]")
|
||||
examples.load_echarts_dashboard()
|
||||
else:
|
||||
print("Loading [Random long/lat data]")
|
||||
examples.load_long_lat_data(only_metadata, force)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from .birth_names import load_birth_names
|
|||
from .country_map import load_country_map_data
|
||||
from .css_templates import load_css_templates
|
||||
from .deck import load_deck_dash
|
||||
from .echarts_dashboard import load_echarts_dashboard
|
||||
from .energy import load_energy
|
||||
from .flights import load_flights
|
||||
from .long_lat import load_long_lat_data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
# 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 json
|
||||
import textwrap
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import inspect
|
||||
|
||||
from superset import db, security_manager
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.utils.core import DatasourceType
|
||||
|
||||
from ..utils.database import get_example_database
|
||||
from .helpers import (
|
||||
get_slice_json,
|
||||
get_table_connector_registry,
|
||||
merge_slice,
|
||||
update_slice_ids,
|
||||
)
|
||||
|
||||
DASH_SLUG = "echarts_dash"
|
||||
|
||||
|
||||
def create_slices(tbl: SqlaTable) -> List[Slice]:
|
||||
admin = security_manager.find_user("admin")
|
||||
slice_props = dict(
|
||||
datasource_id=tbl.id,
|
||||
datasource_type=DatasourceType.TABLE,
|
||||
owners=[admin],
|
||||
created_by=admin,
|
||||
)
|
||||
|
||||
defaults = {
|
||||
"limit": "25",
|
||||
"time_range": "100 years ago : now",
|
||||
"granularity_sqla": "ds",
|
||||
"groupby": ["gender"],
|
||||
"row_limit": "50000",
|
||||
"viz_type": "echarts_timeseries_bar",
|
||||
}
|
||||
|
||||
slices = [
|
||||
Slice(
|
||||
**slice_props,
|
||||
slice_name="Time-Series Bar Chart V2",
|
||||
viz_type="echarts_timeseries_bar",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
adhoc_filters=[
|
||||
{
|
||||
"clause": "WHERE",
|
||||
"expressionType": "SIMPLE",
|
||||
"filterOptionName": "filter_i7pmq9ob0vg_lvnj4s14yt",
|
||||
"comparator": "10000",
|
||||
"operator": ">",
|
||||
"subject": "num_boys",
|
||||
}
|
||||
],
|
||||
viz_type="dist_bar",
|
||||
metrics=["sum__num"],
|
||||
groupby=["gender"],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
for slc in slices:
|
||||
merge_slice(slc)
|
||||
|
||||
return slices
|
||||
|
||||
|
||||
def load_echarts_dashboard() -> None:
|
||||
"""Loading a dashboard featuring EChart charts"""
|
||||
|
||||
database = get_example_database()
|
||||
engine = database.get_sqla_engine()
|
||||
schema = inspect(engine).default_schema_name
|
||||
|
||||
tbl_name = "birth_names"
|
||||
table_exists = database.has_table_by_name(tbl_name, schema=schema)
|
||||
|
||||
if table_exists:
|
||||
table = get_table_connector_registry()
|
||||
obj = (
|
||||
db.session.query(table)
|
||||
.filter_by(table_name=tbl_name, schema=schema)
|
||||
.first()
|
||||
)
|
||||
create_slices(obj)
|
||||
|
||||
print("Creating the dashboard")
|
||||
|
||||
db.session.expunge_all()
|
||||
dash = db.session.query(Dashboard).filter_by(slug=DASH_SLUG).first()
|
||||
|
||||
if not dash:
|
||||
dash = Dashboard()
|
||||
|
||||
js = textwrap.dedent(
|
||||
"""\
|
||||
{
|
||||
"CHART-dxV7Il74hH": {
|
||||
"children": [],
|
||||
"id": "CHART-dxV7Il74hH",
|
||||
"meta": {
|
||||
"chartId": 597,
|
||||
"height": 50,
|
||||
"sliceName": "Box plot",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-YyHWQacdcj": {
|
||||
"children": [],
|
||||
"id": "CHART-YyHWQacdcj",
|
||||
"meta": {
|
||||
"chartId": 15,
|
||||
"height": 50,
|
||||
"sliceName": "Participants",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-oWKBOJ6Ydh": {
|
||||
"children": [],
|
||||
"id": "CHART-oWKBOJ6Ydh",
|
||||
"meta":{
|
||||
"chartId": 16,
|
||||
"height": 50,
|
||||
"sliceName": "Genders",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-06Kg-rUggO": {
|
||||
"children": [],
|
||||
"id": "CHART-06Kg-rUggO",
|
||||
"meta": {
|
||||
"chartId": 617,
|
||||
"height": 50,
|
||||
"sliceName": "Number of Girls",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART--wEhS-MDSg": {
|
||||
"children": [],
|
||||
"id": "CHART--wEhS-MDS",
|
||||
"meta": {
|
||||
"chartId": 2,
|
||||
"height": 50,
|
||||
"sliceName": "Energy Force Layout",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART--LXvS-RDSu": {
|
||||
"children": [],
|
||||
"id": "CHART--LXvS-RDSu",
|
||||
"meta": {
|
||||
"chartId": 398,
|
||||
"height": 50,
|
||||
"sliceName": "Time-Series Bar Chart V2",
|
||||
"width": 6
|
||||
},
|
||||
"type": "CHART"
|
||||
},
|
||||
"GRID_ID": {
|
||||
"children": [
|
||||
"ROW-SytNzNA4X",
|
||||
"ROW-HkFFEzVRVm",
|
||||
"ROW-BytNzNA4Y"
|
||||
],
|
||||
"id": "GRID_ID",
|
||||
"type": "GRID"
|
||||
},
|
||||
"HEADER_ID": {
|
||||
"id": "HEADER_ID",
|
||||
"meta": {
|
||||
"text": "ECharts Dashboard"
|
||||
},
|
||||
"type": "HEADER"
|
||||
},
|
||||
"ROOT_ID": {
|
||||
"children": [
|
||||
"GRID_ID"
|
||||
],
|
||||
"id": "ROOT_ID",
|
||||
"type": "ROOT"
|
||||
},
|
||||
"ROW-HkFFEzVRVm": {
|
||||
"children": [
|
||||
"CHART-dxV7Il74hH",
|
||||
"CHART-oWKBOJ6Ydh"
|
||||
],
|
||||
"id": "ROW-HkFFEzVRVm",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"type": "ROW"
|
||||
},
|
||||
"ROW-SytNzNA4X": {
|
||||
"children": [
|
||||
"CHART-06Kg-rUggO",
|
||||
"CHART-YyHWQacdcj"
|
||||
],
|
||||
"id": "ROW-SytNzNA4X",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"type": "ROW"
|
||||
},
|
||||
"ROW-BytNzNA4Y": {
|
||||
"children": [
|
||||
"CHART--wEhS-MDSg",
|
||||
"CHART--LXvS-RDSu"
|
||||
],
|
||||
"id": "ROW-BytNzNA4Y",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"type": "ROW"
|
||||
},
|
||||
"DASHBOARD_VERSION_KEY": "v2"
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
pos = json.loads(js)
|
||||
dash.slices = update_slice_ids(pos)
|
||||
dash.dashboard_title = "ECharts Dashboard"
|
||||
dash.position_json = json.dumps(pos, indent=4)
|
||||
dash.slug = DASH_SLUG
|
||||
db.session.commit()
|
||||
|
|
@ -70,6 +70,7 @@ FEATURE_FLAGS = {
|
|||
"ENABLE_TEMPLATE_PROCESSING": True,
|
||||
"ALERT_REPORTS": True,
|
||||
"DASHBOARD_NATIVE_FILTERS": True,
|
||||
"DRILL_TO_DETAIL": True,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue