Timeline: Add timeRangePan (#113890)
* Timeline: Add timeRangePan * Add tests for state timeline * Add tests for status history * Fix state timeline test and use same dashboard * Consolidate tests * Optimize time range pan tests
This commit is contained in:
@@ -87,3 +87,112 @@ test.describe('Panels test: StateTimeine', { tag: ['@panels', '@state-timeline']
|
||||
await expect(tooltip, 'tooltip is not shown when disabled').toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
timeRangePan: true,
|
||||
},
|
||||
});
|
||||
|
||||
test.describe('Panels test: State Timeline X-axis panning', { tag: ['@panels', '@state-timeline'] }, () => {
|
||||
test('x-axis panning functionality', async ({ gotoDashboardPage, page, selectors }) => {
|
||||
let centerX: number;
|
||||
let centerY: number;
|
||||
let initialFromTime: number;
|
||||
let initialToTime: number;
|
||||
|
||||
const dashboardPage = await test.step('Load dashboard and verify cursor changes to grab', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UID });
|
||||
|
||||
const stateTimelinePanel = page.locator('.uplot').first();
|
||||
await expect(stateTimelinePanel, 'panel rendered').toBeVisible();
|
||||
|
||||
const xAxis = stateTimelinePanel.locator('.u-axis').first();
|
||||
await expect(xAxis, 'x-axis rendered').toBeVisible();
|
||||
|
||||
await xAxis.hover();
|
||||
|
||||
const cursorStyle = await xAxis.evaluate((el: HTMLElement) => window.getComputedStyle(el).cursor);
|
||||
expect(cursorStyle, 'cursor is grab').toBe('grab');
|
||||
|
||||
return dashboardPage;
|
||||
});
|
||||
|
||||
await test.step('Capture initial time range', async () => {
|
||||
const stateTimelinePanel = page.locator('.uplot').first();
|
||||
const xAxis = stateTimelinePanel.locator('.u-axis').first();
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const initialFrom = await fromField.inputValue();
|
||||
const initialTo = await toField.inputValue();
|
||||
initialFromTime = new Date(initialFrom).getTime();
|
||||
initialToTime = new Date(initialTo).getTime();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const axisBox = await xAxis.boundingBox();
|
||||
if (!axisBox) {
|
||||
throw new Error('X-axis bounding box not found');
|
||||
}
|
||||
|
||||
centerX = axisBox.x + axisBox.width / 2;
|
||||
centerY = axisBox.y + axisBox.height / 2;
|
||||
});
|
||||
|
||||
await test.step('Drag right pans backward in time', async () => {
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 100, centerY);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const afterRightFrom = await fromField.inputValue();
|
||||
const afterRightTo = await toField.inputValue();
|
||||
const afterRightFromTime = new Date(afterRightFrom).getTime();
|
||||
const afterRightToTime = new Date(afterRightTo).getTime();
|
||||
|
||||
expect(afterRightFromTime, 'panned backward').toBeLessThan(initialFromTime);
|
||||
expect(afterRightToTime, 'panned backward').toBeLessThan(initialToTime);
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
initialFromTime = afterRightFromTime;
|
||||
initialToTime = afterRightToTime;
|
||||
});
|
||||
|
||||
await test.step('Drag left pans forward in time', async () => {
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX - 100, centerY);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const afterLeftFrom = await fromField.inputValue();
|
||||
const afterLeftTo = await toField.inputValue();
|
||||
const afterLeftFromTime = new Date(afterLeftFrom).getTime();
|
||||
const afterLeftToTime = new Date(afterLeftTo).getTime();
|
||||
|
||||
expect(afterLeftFromTime, 'panned forward').toBeGreaterThan(initialFromTime);
|
||||
expect(afterLeftToTime, 'panned forward').toBeGreaterThan(initialToTime);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,3 +85,112 @@ test.describe('Panels test: StatusHistory', { tag: ['@panels', '@status-history'
|
||||
await expect(tooltip, 'tooltip is not shown when disabled').toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
timeRangePan: true,
|
||||
},
|
||||
});
|
||||
|
||||
test.describe('Panels test: Status History X-axis panning', { tag: ['@panels', '@status-history'] }, () => {
|
||||
test('x-axis panning functionality', async ({ gotoDashboardPage, page, selectors }) => {
|
||||
let centerX: number;
|
||||
let centerY: number;
|
||||
let initialFromTime: number;
|
||||
let initialToTime: number;
|
||||
|
||||
const dashboardPage = await test.step('Load dashboard and verify cursor changes to grab', async () => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UID });
|
||||
|
||||
const statusHistoryPanel = page.locator('.uplot').first();
|
||||
await expect(statusHistoryPanel, 'panel rendered').toBeVisible();
|
||||
|
||||
const xAxis = statusHistoryPanel.locator('.u-axis').first();
|
||||
await expect(xAxis, 'x-axis rendered').toBeVisible();
|
||||
|
||||
await xAxis.hover();
|
||||
|
||||
const cursorStyle = await xAxis.evaluate((el: HTMLElement) => window.getComputedStyle(el).cursor);
|
||||
expect(cursorStyle, 'cursor is grab').toBe('grab');
|
||||
|
||||
return dashboardPage;
|
||||
});
|
||||
|
||||
await test.step('Capture initial time range', async () => {
|
||||
const statusHistoryPanel = page.locator('.uplot').first();
|
||||
const xAxis = statusHistoryPanel.locator('.u-axis').first();
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const initialFrom = await fromField.inputValue();
|
||||
const initialTo = await toField.inputValue();
|
||||
initialFromTime = new Date(initialFrom).getTime();
|
||||
initialToTime = new Date(initialTo).getTime();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const axisBox = await xAxis.boundingBox();
|
||||
if (!axisBox) {
|
||||
throw new Error('X-axis bounding box not found');
|
||||
}
|
||||
|
||||
centerX = axisBox.x + axisBox.width / 2;
|
||||
centerY = axisBox.y + axisBox.height / 2;
|
||||
});
|
||||
|
||||
await test.step('Drag right pans backward in time', async () => {
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX + 100, centerY);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const afterRightFrom = await fromField.inputValue();
|
||||
const afterRightTo = await toField.inputValue();
|
||||
const afterRightFromTime = new Date(afterRightFrom).getTime();
|
||||
const afterRightToTime = new Date(afterRightTo).getTime();
|
||||
|
||||
expect(afterRightFromTime, 'panned backward').toBeLessThan(initialFromTime);
|
||||
expect(afterRightToTime, 'panned backward').toBeLessThan(initialToTime);
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
initialFromTime = afterRightFromTime;
|
||||
initialToTime = afterRightToTime;
|
||||
});
|
||||
|
||||
await test.step('Drag left pans forward in time', async () => {
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX - 100, centerY);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const timePickerButton = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.openButton);
|
||||
await timePickerButton.click();
|
||||
|
||||
const fromField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.fromField);
|
||||
const toField = dashboardPage.getByGrafanaSelector(selectors.components.TimePicker.toField);
|
||||
|
||||
const afterLeftFrom = await fromField.inputValue();
|
||||
const afterLeftTo = await toField.inputValue();
|
||||
const afterLeftFromTime = new Date(afterLeftFrom).getTime();
|
||||
const afterLeftToTime = new Date(afterLeftTo).getTime();
|
||||
|
||||
expect(afterLeftFromTime, 'panned forward').toBeGreaterThan(initialFromTime);
|
||||
expect(afterLeftToTime, 'panned forward').toBeGreaterThan(initialToTime);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,7 +156,13 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
|
||||
isTime: true,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
range: coreConfig.xRange,
|
||||
range: (u) => {
|
||||
const state = builder.getState();
|
||||
if (state.isPanning) {
|
||||
return [state.min, state.max];
|
||||
}
|
||||
return coreConfig.xRange(u);
|
||||
},
|
||||
});
|
||||
|
||||
builder.addScale({
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
EventBusPlugin,
|
||||
TooltipDisplayMode,
|
||||
TooltipPlugin2,
|
||||
XAxisInteractionAreaPlugin,
|
||||
usePanelContext,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
@@ -102,6 +103,7 @@ export const StateTimelinePanel = ({
|
||||
{cursorSync !== DashboardCursorSync.Off && (
|
||||
<EventBusPlugin config={builder} eventBus={eventBus} frame={alignedFrame} />
|
||||
)}
|
||||
<XAxisInteractionAreaPlugin config={builder} queryZoom={onChangeTimeRange} />
|
||||
{options.tooltip.mode !== TooltipDisplayMode.None && (
|
||||
<TooltipPlugin2
|
||||
config={builder}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TooltipPlugin2,
|
||||
usePanelContext,
|
||||
useTheme2,
|
||||
XAxisInteractionAreaPlugin,
|
||||
} from '@grafana/ui';
|
||||
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/internal';
|
||||
import { TimelineChart } from 'app/core/components/TimelineChart/TimelineChart';
|
||||
@@ -117,6 +118,7 @@ export const StatusHistoryPanel = ({
|
||||
{cursorSync !== DashboardCursorSync.Off && (
|
||||
<EventBusPlugin config={builder} eventBus={eventBus} frame={alignedFrame} />
|
||||
)}
|
||||
<XAxisInteractionAreaPlugin config={builder} queryZoom={onChangeTimeRange} />
|
||||
{options.tooltip.mode !== TooltipDisplayMode.None && (
|
||||
<TooltipPlugin2
|
||||
config={builder}
|
||||
|
||||
Reference in New Issue
Block a user