mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Timeseries: Time regions migration (#66998)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
5c4ecf7a86
commit
2beee35567
@ -5922,20 +5922,23 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "6"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "8"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "10"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "12"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "13"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "9"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "11"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "12"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "13"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "14"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "15"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "17"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "18"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "19"]
|
||||
],
|
||||
"public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
|
52
e2e/various-suite/graph-auto-migrate.spec.ts
Normal file
52
e2e/various-suite/graph-auto-migrate.spec.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
const DASHBOARD_ID = 'XMjIZPmik';
|
||||
const DASHBOARD_NAME = 'Panel Tests - Graph Time Regions';
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Auto-migrate graph panel',
|
||||
itName: 'Annotation markers exist for time regions',
|
||||
addScenarioDataSource: false,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
e2e().contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.contains('uplot-main-div').should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
|
||||
|
||||
e2e().wait(1000);
|
||||
|
||||
e2e.components.Panels.Panel.title('Business Hours')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.Annotations.marker().should('exist');
|
||||
});
|
||||
|
||||
e2e.components.Panels.Panel.title("Sunday's 20-23")
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.Annotations.marker().should('exist');
|
||||
});
|
||||
|
||||
e2e.components.Panels.Panel.title('Each day of week')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.Annotations.marker().should('exist');
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.wrapper().children().children('.scrollbar-view').scrollTo('bottom');
|
||||
|
||||
e2e.components.Panels.Panel.title('05:00')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.Annotations.marker().should('exist');
|
||||
});
|
||||
|
||||
e2e.components.Panels.Panel.title('From 22:00 to 00:30 (crossing midnight)')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
e2e.pages.Dashboard.Annotations.marker().should('exist');
|
||||
});
|
||||
},
|
||||
});
|
@ -49,6 +49,7 @@ export const Pages = {
|
||||
},
|
||||
Dashboard: {
|
||||
url: (uid: string) => `/d/${uid}`,
|
||||
wrapper: 'data-testid dashboard-page-wrapper',
|
||||
DashNav: {
|
||||
/**
|
||||
* @deprecated use navV2 from Grafana 8.3 instead
|
||||
|
@ -237,7 +237,7 @@ function overrideFeatureTogglesFromUrl(config: GrafanaBootConfig) {
|
||||
if (key.startsWith('__feature.')) {
|
||||
const featureToggles = config.featureToggles as Record<string, boolean>;
|
||||
const featureName = key.substring(10);
|
||||
const toggleState = value === 'true';
|
||||
const toggleState = value === 'true' || value === ''; // browser rewrites true as ''
|
||||
if (toggleState !== featureToggles[key]) {
|
||||
featureToggles[featureName] = toggleState;
|
||||
console.log(`Setting feature toggle ${featureName} = ${toggleState}`);
|
||||
|
@ -3,6 +3,7 @@ import { css, cx } from '@emotion/css';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
@ -50,7 +51,7 @@ export const Page: PageType = ({
|
||||
}, [navModel, pageNav, chrome, layout]);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps}>
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps} data-testid={selectors.pages.Dashboard.wrapper}>
|
||||
{layout === PageLayoutType.Standard && (
|
||||
<CustomScrollbar autoHeightMin={'100%'} scrollTop={scrollTop} scrollRefCallback={scrollRef}>
|
||||
<div className={styles.pageInner}>
|
||||
|
@ -9,7 +9,7 @@
|
||||
Migrate
|
||||
</button>
|
||||
</p>
|
||||
<p>Some features like colored time regions and negative transforms are not supported in the new panel yet.</p>
|
||||
<p>Some features are not supported in the new panel yet.</p>
|
||||
</div>
|
||||
|
||||
<gf-form-switch
|
||||
|
@ -521,6 +521,37 @@ exports[`Graph Migrations stepped line 1`] = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Graph Migrations time regions should migrate 1`] = `
|
||||
{
|
||||
"alert": undefined,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "points",
|
||||
"spanNulls": false,
|
||||
},
|
||||
},
|
||||
"overrides": [],
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Graph Migrations transforms should preserve "constant" transform 1`] = `
|
||||
{
|
||||
"defaults": {
|
||||
|
@ -2,17 +2,32 @@ import { cloneDeep } from 'lodash';
|
||||
|
||||
import { PanelModel, FieldConfigSource, FieldMatcherID, ReducerID } from '@grafana/data';
|
||||
import { TooltipDisplayMode, SortOrder } from '@grafana/schema';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardModel, PanelModel as PanelModelState } from 'app/features/dashboard/state';
|
||||
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
|
||||
describe('Graph Migrations', () => {
|
||||
let prevFieldConfig: FieldConfigSource;
|
||||
let dashboard: DashboardModel;
|
||||
|
||||
beforeEach(() => {
|
||||
prevFieldConfig = {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
};
|
||||
|
||||
dashboard = createDashboardModelFixture({
|
||||
id: 74,
|
||||
version: 7,
|
||||
annotations: {},
|
||||
links: [],
|
||||
panels: [],
|
||||
});
|
||||
|
||||
getDashboardSrv().setCurrent(dashboard);
|
||||
});
|
||||
|
||||
it('simple bars', () => {
|
||||
@ -82,6 +97,36 @@ describe('Graph Migrations', () => {
|
||||
expect(panel.fieldConfig.overrides[1].matcher.id).toBe(FieldMatcherID.byRegexp);
|
||||
});
|
||||
|
||||
describe('time regions', () => {
|
||||
test('should migrate', () => {
|
||||
const old = {
|
||||
angular: {
|
||||
timeRegions: [
|
||||
{
|
||||
colorMode: 'red',
|
||||
fill: true,
|
||||
fillColor: 'rgba(234, 112, 112, 0.12)',
|
||||
fromDayOfWeek: 1,
|
||||
line: true,
|
||||
lineColor: 'rgba(237, 46, 24, 0.60)',
|
||||
op: 'time',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const panel = { datasource: { type: 'datasource', uid: 'gdev-testdata' } } as PanelModel;
|
||||
dashboard.panels.push(new PanelModelState(panel));
|
||||
panel.options = graphPanelChangedHandler(panel, 'graph', old, prevFieldConfig);
|
||||
expect(dashboard.panels).toHaveLength(1);
|
||||
expect(dashboard.annotations.list).toHaveLength(2); // built-in + time region
|
||||
expect(
|
||||
dashboard.annotations.list.filter((annotation) => annotation.target?.queryType === GrafanaQueryType.TimeRegions)
|
||||
).toHaveLength(1);
|
||||
expect(panel).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('legend', () => {
|
||||
test('without values', () => {
|
||||
const old = {
|
||||
|
@ -31,12 +31,19 @@ import {
|
||||
StackingMode,
|
||||
SortOrder,
|
||||
GraphTransform,
|
||||
AnnotationQuery,
|
||||
ComparisonOperation,
|
||||
} from '@grafana/schema';
|
||||
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||
|
||||
import { defaultGraphConfig } from './config';
|
||||
import { PanelOptions } from './panelcfg.gen';
|
||||
|
||||
let dashboardRefreshDebouncer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/**
|
||||
* This is called when the panel changes from another panel
|
||||
*/
|
||||
@ -48,10 +55,25 @@ export const graphPanelChangedHandler: PanelTypeChangedHandler = (
|
||||
) => {
|
||||
// Changing from angular/flot panel to react/uPlot
|
||||
if (prevPluginId === 'graph' && prevOptions.angular) {
|
||||
const { fieldConfig, options } = graphToTimeseriesOptions({
|
||||
const { fieldConfig, options, annotations } = graphToTimeseriesOptions({
|
||||
...prevOptions.angular,
|
||||
fieldConfig: prevFieldConfig,
|
||||
panel: panel,
|
||||
});
|
||||
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
if (dashboard && annotations?.length > 0) {
|
||||
dashboard.annotations.list = [...dashboard.annotations.list, ...annotations];
|
||||
|
||||
// Trigger a full dashboard refresh when annotations change
|
||||
if (dashboardRefreshDebouncer == null) {
|
||||
dashboardRefreshDebouncer = setTimeout(() => {
|
||||
dashboardRefreshDebouncer = null;
|
||||
getTimeSrv().refreshTimeModel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
|
||||
panel.alert = prevOptions.angular.alert;
|
||||
return options;
|
||||
@ -63,7 +85,13 @@ export const graphPanelChangedHandler: PanelTypeChangedHandler = (
|
||||
return {};
|
||||
};
|
||||
|
||||
export function graphToTimeseriesOptions(angular: any): { fieldConfig: FieldConfigSource; options: PanelOptions } {
|
||||
export function graphToTimeseriesOptions(angular: any): {
|
||||
fieldConfig: FieldConfigSource;
|
||||
options: PanelOptions;
|
||||
annotations: AnnotationQuery[];
|
||||
} {
|
||||
let annotations: AnnotationQuery[] = [];
|
||||
|
||||
const overrides: ConfigOverrideRule[] = angular.fieldConfig?.overrides ?? [];
|
||||
const yaxes = angular.yaxes ?? [];
|
||||
let y1 = getFieldConfigFromOldAxis(yaxes[0]);
|
||||
@ -362,6 +390,55 @@ export function graphToTimeseriesOptions(angular: any): { fieldConfig: FieldConf
|
||||
}
|
||||
}
|
||||
|
||||
// timeRegions migration
|
||||
if (angular.timeRegions?.length) {
|
||||
let regions: any[] = angular.timeRegions.map((old: GraphTimeRegionConfig, idx: number) => ({
|
||||
name: `T${idx + 1}`,
|
||||
color: old.colorMode !== 'custom' ? old.colorMode : old.fillColor,
|
||||
line: old.line,
|
||||
fill: old.fill,
|
||||
fromDayOfWeek: old.fromDayOfWeek,
|
||||
toDayOfWeek: old.toDayOfWeek,
|
||||
from: old.from,
|
||||
to: old.to,
|
||||
}));
|
||||
|
||||
regions.forEach((region: GraphTimeRegionConfig, idx: number) => {
|
||||
const anno: AnnotationQuery<GrafanaQuery> = {
|
||||
datasource: {
|
||||
type: 'datasource',
|
||||
uid: 'grafana',
|
||||
},
|
||||
enable: true,
|
||||
hide: true, // don't show the toggle at the top of the dashboard
|
||||
filter: {
|
||||
exclude: false,
|
||||
ids: [angular.panel.id],
|
||||
},
|
||||
iconColor: region.fillColor ?? (region as any).color,
|
||||
name: `T${idx + 1}`,
|
||||
target: {
|
||||
queryType: GrafanaQueryType.TimeRegions,
|
||||
refId: 'Anno',
|
||||
timeRegion: {
|
||||
fromDayOfWeek: region.fromDayOfWeek,
|
||||
toDayOfWeek: region.toDayOfWeek,
|
||||
from: region.from,
|
||||
to: region.to,
|
||||
timezone: 'utc', // graph panel was always UTC
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (region.fill) {
|
||||
annotations.push(anno);
|
||||
} else if (region.line) {
|
||||
anno.iconColor = region.lineColor ?? 'white';
|
||||
annotations.push(anno);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tooltipConfig = angular.tooltip;
|
||||
if (tooltipConfig) {
|
||||
if (tooltipConfig.shared !== undefined) {
|
||||
@ -479,9 +556,18 @@ export function graphToTimeseriesOptions(angular: any): { fieldConfig: FieldConf
|
||||
overrides,
|
||||
},
|
||||
options,
|
||||
annotations,
|
||||
};
|
||||
}
|
||||
|
||||
interface GraphTimeRegionConfig extends TimeRegionConfig {
|
||||
colorMode: string;
|
||||
fill: boolean;
|
||||
fillColor: string;
|
||||
line: boolean;
|
||||
lineColor: string;
|
||||
}
|
||||
|
||||
function getThresholdColor(threshold: AngularThreshold): string {
|
||||
if (threshold.colorMode === 'critical') {
|
||||
return 'red';
|
||||
|
Loading…
Reference in New Issue
Block a user