Table: Enable tableNextGen by default (#109832)

* Table: Enable tableNextGen by default

* kill off tableNextGen feature flag

* i18n

* i18n

* i18n

* remove state beta from table panel

* fix migration for 0 decimals migration

* add explicit auto option bacjk

* match impl from previous migrations code

* try changing some selectors

* remove timezone test from Cypress

* fix PlaylistForm unit test

* fix some selectors

* clean up i18n, are these gridcells somehow?

* return a couple of selectors caught in the dragnet to being cell instead of gridcell

* fix i18n for en-US

* clean up gdevs now that wrapHeaderText has no default

* update role in e2e for when it is re-enabled
This commit is contained in:
Paul Marbach
2025-08-26 17:25:16 -04:00
committed by GitHub
parent 1c13f4a0f2
commit ec38e0bd58
60 changed files with 253 additions and 2650 deletions
@@ -1,16 +0,0 @@
import testDashboard from '../dashboards/DashboardLiveTest.json';
import { e2e } from '../utils';
describe('Dashboard Live streaming support', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
e2e.flows.importDashboard(testDashboard, 1000);
});
it('Should receive streaming data', () => {
e2e.flows.openDashboard({ uid: 'live-e2e-test', queryParams: { '__feature.tableNextGen': false } });
cy.wait(1000);
e2e.components.Panels.Panel.title('Live').should('exist');
e2e.components.Panels.Visualization.Table.body().find('[role="row"]').should('have.length.at.least', 5);
});
});
@@ -1,263 +0,0 @@
import {
addDays,
addHours,
differenceInCalendarDays,
differenceInMinutes,
format,
isBefore,
parseISO,
toDate,
} from 'date-fns';
import { e2e } from '../utils';
describe('Dashboard time zone support', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it.skip('Tests dashboard time zone scenarios', () => {
e2e.flows.openDashboard({ uid: '5SdHCasdf' });
const fromTimeZone = 'UTC';
const toTimeZone = 'America/Chicago';
const offset = offsetBetweenTimeZones(toTimeZone, fromTimeZone);
const panelsToCheck = [
'Random walk series',
'Millisecond res x-axis and tooltip',
'2 yaxis and axis labels',
'Stacking value ontop of nulls',
'Null between points',
'Legend Table No Scroll Visible',
];
const timesInUtc: Record<string, string> = {};
for (const title of panelsToCheck) {
e2e.components.Panels.Panel.title(title)
.should('be.visible')
.within(() => {
e2e.components.Panels.Visualization.Graph.xAxis.labels().should('be.visible');
e2e.components.Panels.Visualization.Graph.xAxis
.labels()
.last()
.should((element) => {
timesInUtc[title] = element.text();
});
});
}
e2e.components.PageToolbar.item('Dashboard settings').click();
e2e.components.TimeZonePicker.containerV2()
.should('be.visible')
.within(() => {
e2e.components.Select.singleValue().should('have.text', 'Coordinated Universal Time');
e2e.components.Select.input().should('be.visible').click();
});
e2e.components.Select.option().should('be.visible').contains(toTimeZone).click();
// click to go back to the dashboard.
e2e.pages.Dashboard.Settings.Actions.close().click();
e2e.components.RefreshPicker.runButtonV2().should('be.visible').click();
for (const title of panelsToCheck) {
e2e.components.Panels.Panel.title(title)
.should('be.visible')
.within(() => {
e2e.components.Panels.Visualization.Graph.xAxis.labels().should('be.visible');
e2e.components.Panels.Visualization.Graph.xAxis
.labels()
.last()
.should((element) => {
const inUtc = timesInUtc[title];
const inTz = element.text();
const isCorrect = isTimeCorrect(inUtc, inTz, offset);
expect(isCorrect).to.be.equal(true);
});
});
}
});
it('Tests relative timezone support and overrides', () => {
// Open dashboard
e2e.flows.openDashboard({
uid: 'd41dbaa2-a39e-4536-ab2b-caca52f1a9c8',
});
cy.intercept('/api/ds/query*').as('dataQuery');
// Switch to Browser timezone
e2e.flows.setTimeRange({
from: 'now-6h',
to: 'now',
zone: 'Browser',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Today so far, still in Browser timezone
e2e.flows.setTimeRange({
from: 'now/d',
to: 'now',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
e2e.components.Panels.Panel.title('Panel in timezone')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Test UTC timezone
e2e.flows.setTimeRange({
from: 'now-6h',
to: 'now',
zone: 'Coordinated Universal Time',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Today so far, still in UTC timezone
e2e.flows.setTimeRange({
from: 'now/d',
to: 'now',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
e2e.components.Panels.Panel.title('Panel in timezone')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Test Tokyo timezone
e2e.flows.setTimeRange({
from: 'now-6h',
to: 'now',
zone: 'Asia/Tokyo',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Today so far, still in Tokyo timezone
e2e.flows.setTimeRange({
from: 'now/d',
to: 'now',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
e2e.components.Panels.Panel.title('Panel in timezone')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Test LA timezone
e2e.flows.setTimeRange({
from: 'now-6h',
to: 'now',
zone: 'America/Los Angeles',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
// Today so far, still in LA timezone
e2e.flows.setTimeRange({
from: 'now/d',
to: 'now',
});
// Need to wait for 2 calls as there's 2 panels
cy.wait(['@dataQuery', '@dataQuery']);
e2e.components.Panels.Panel.title('Panel with relative time override')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
e2e.components.Panels.Panel.title('Panel in timezone')
.should('be.visible')
.within(() => {
cy.contains('[role="row"]', '00:00:00').should('be.visible');
});
});
});
const isTimeCorrect = (inUtc: string, inTz: string, offset: number): boolean => {
if (inUtc === inTz) {
// we need to catch issues when timezone isn't changed for some reason like https://github.com/grafana/grafana/issues/35504
return false;
}
const reference = format(new Date(), 'yyyy-LL-dd');
const utcDate = toDate(parseISO(`${reference} ${inUtc}`));
const utcDateWithOffset = addHours(toDate(parseISO(`${reference} ${inUtc}`)), offset);
const dayDifference = differenceInCalendarDays(utcDate, utcDateWithOffset); // if the utcDate +/- offset is the day before/after then we need to adjust reference
const dayOffset = isBefore(utcDateWithOffset, utcDate) ? dayDifference * -1 : dayDifference;
const tzDate = addDays(toDate(parseISO(`${reference} ${inTz}`)), dayOffset); // adjust tzDate with any dayOffset
const diff = Math.abs(differenceInMinutes(utcDate, tzDate)); // use Math.abs if tzDate is in future
return diff <= Math.abs(offset * 60);
};
const offsetBetweenTimeZones = (timeZone1: string, timeZone2: string, when: Date = new Date()): number => {
const t1 = convertDateToAnotherTimeZone(when, timeZone1);
const t2 = convertDateToAnotherTimeZone(when, timeZone2);
return (t1.getTime() - t2.getTime()) / (1000 * 60 * 60);
};
const convertDateToAnotherTimeZone = (date: Date, timeZone: string): Date => {
const dateString = date.toLocaleString('en-US', {
timeZone: timeZone,
});
return new Date(dateString);
};
@@ -1,98 +0,0 @@
import { e2e } from '../utils';
const DASHBOARD_ID = 'P2jR04WVk';
describe.skip('Geomap spatial operations', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it.skip('Tests location auto option', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } });
e2e.components.Tab.title('Transform data').should('be.visible').click();
e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click();
e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click();
e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}');
e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.locationLabel().type('Auto{enter}');
e2e.components.PanelEditor.toggleTableView().click({ force: true });
e2e.components.Panels.Visualization.Table.header()
.should('be.visible')
.within(() => {
cy.contains('Point').should('be.visible');
});
});
it('Tests location coords option', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } });
e2e.components.Tab.title('Transform data').should('be.visible').click();
e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click();
e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click();
e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}');
e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.locationLabel().type('Coords{enter}');
e2e.components.Transforms.SpatialOperations.location.coords.latitudeFieldLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.location.coords.latitudeFieldLabel().type('Lat{enter}');
e2e.components.Transforms.SpatialOperations.location.coords.longitudeFieldLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.location.coords.longitudeFieldLabel().type('Lng{enter}');
e2e.components.PanelEditor.toggleTableView().click({ force: true });
e2e.components.Panels.Visualization.Table.header()
.should('be.visible')
.within(() => {
cy.contains('Point').should('be.visible');
});
});
it('Tests geoshash field column appears in table view', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } });
e2e.components.Tab.title('Transform data').should('be.visible').click();
e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click();
e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click();
e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}');
e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.locationLabel().type('Geohash{enter}');
e2e.components.Transforms.SpatialOperations.location.geohash
.geohashFieldLabel()
.should('be.visible')
.type('State{enter}');
e2e.components.PanelEditor.toggleTableView().click({ force: true });
e2e.components.Panels.Visualization.Table.header()
.should('be.visible')
.within(() => {
cy.contains('State 1').should('be.visible');
});
});
it('Tests location lookup option', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } });
e2e.components.Tab.title('Transform data').should('be.visible').click();
e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click();
e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click();
e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}');
e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.locationLabel().type('Lookup{enter}');
e2e.components.Transforms.SpatialOperations.location.lookup.lookupFieldLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.location.lookup.lookupFieldLabel().type('State{enter}');
e2e.components.Transforms.SpatialOperations.location.lookup.gazetteerFieldLabel().should('be.visible');
e2e.components.Transforms.SpatialOperations.location.lookup.gazetteerFieldLabel().type('USA States{enter}');
e2e.components.PanelEditor.toggleTableView().click({ force: true });
e2e.components.Panels.Visualization.Table.header()
.should('be.visible')
.within(() => {
cy.contains('Geometry').should('be.visible');
});
});
});
@@ -1,108 +0,0 @@
import { e2e } from '../utils';
const PANEL_UNDER_TEST = 'Lines 500 data points';
describe('Panel edit tests', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Tests various Panel edit scenarios', () => {
cy.intercept({
pathname: '/api/ds/query',
}).as('query');
e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { '__feature.tableNextGen': false } });
cy.wait('@query');
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
// New panel editor opens when navigating from Panel menu
e2e.components.PanelEditor.General.content().should('be.visible');
// Queries tab is rendered and open by default
e2e.components.PanelEditor.DataPane.content()
.should('be.visible')
.within(() => {
e2e.components.Tab.title('Query').should('be.visible');
// data should be the active tab
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Query1'); // there's already a query so therefore Query + 1
});
e2e.components.QueryTab.content().should('be.visible');
e2e.components.TransformTab.content().should('not.exist');
e2e.components.AlertTab.content().should('not.exist');
e2e.components.PanelAlertTabContent.content().should('not.exist');
// Bottom pane tabs
// Can change to Transform tab
e2e.components.Tab.title('Transform data').should('be.visible').click();
e2e.components.Tab.active().within((li: JQuery<HTMLLIElement>) => {
expect(li.text()).equals('Transform data0'); // there's no transform so therefore Transform + 0
});
e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible');
e2e.components.QueryTab.content().should('not.exist');
e2e.components.AlertTab.content().should('not.exist');
e2e.components.PanelAlertTabContent.content().should('not.exist');
// Can change to Alerts tab (graph panel is the default vis so the alerts tab should be rendered)
e2e.components.Tab.title('Alert').should('be.visible').click();
e2e.components.Tab.active().should('have.text', 'Alert0'); // there's no alert so therefore Alert + 0
// Needs to be disabled until Grafana EE turns unified alerting on by default
// e2e.components.AlertTab.content().should('not.exist');
e2e.components.QueryTab.content().should('not.exist');
e2e.components.TransformTab.content().should('not.exist');
// Needs to be disabled until Grafana EE turns unified alerting on by default
// e2e.components.PanelAlertTabContent.content().should('exist');
// e2e.components.PanelAlertTabContent.content().should('be.visible');
e2e.components.Tab.title('Query').should('be.visible').click();
});
// Panel sidebar is rendered open by default
e2e.components.PanelEditor.OptionsPane.content().should('be.visible');
// close options pane
e2e.components.PanelEditor.toggleVizOptions().click();
e2e.components.PanelEditor.OptionsPane.content().should('not.exist');
// open options pane
e2e.components.PanelEditor.toggleVizOptions().should('be.visible').click();
e2e.components.PanelEditor.OptionsPane.content().should('be.visible');
// Check that Time series is chosen
e2e.components.PanelEditor.toggleVizPicker().click();
e2e.components.PluginVisualization.item('Time series').should('be.visible');
e2e.components.PluginVisualization.current().should((e) => expect(e).to.contain('Time series'));
// Check that table view works
e2e.components.Panels.Panel.loadingBar().should('not.exist');
e2e.components.PanelEditor.toggleTableView().click({ force: true });
e2e.components.Panels.Visualization.Table.header()
.should('be.visible')
.within(() => {
cy.contains('A-series').should('be.visible');
});
// Change to Text panel
e2e.components.PluginVisualization.item('Text').scrollIntoView().should('be.visible').click();
e2e.components.PanelEditor.toggleVizPicker().should((e) => expect(e).to.contain('Text'));
// Data pane should not be rendered
e2e.components.PanelEditor.DataPane.content().should('not.exist');
// Change to Table panel
e2e.components.PanelEditor.toggleVizPicker().click();
e2e.components.PluginVisualization.item('Table').scrollIntoView().should('be.visible').click();
e2e.components.PanelEditor.toggleVizPicker().should((e) => expect(e).to.contain('Table'));
// Data pane should be rendered
e2e.components.PanelEditor.DataPane.content().should('be.visible');
// Field & Overrides tabs (need to switch to React based vis, i.e. Table)
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Show table header').should('be.visible');
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible');
});
});
@@ -1,27 +0,0 @@
import { e2e } from '../utils';
describe('Visualization suggestions', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Should be shown and clickable', () => {
e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { '__feature.tableNextGen': false, editPanel: 9 } });
// Try visualization suggestions
e2e.components.PanelEditor.toggleVizPicker().click();
e2e.components.RadioButton.container().filter(':contains("Suggestions")').click();
// Verify we see suggestions
e2e.components.VisualizationPreview.card('Line chart').should('be.visible');
// Verify search works
cy.get('[placeholder="Search for..."]').type('Table');
// Should no longer see line chart
e2e.components.VisualizationPreview.card('Line chart').should('not.exist');
// Select a visualisation
e2e.components.VisualizationPreview.card('Table').click();
e2e.components.Panels.Visualization.Table.header().should('be.visible');
});
});