grafana/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts

238 lines
6.5 KiB
TypeScript
Raw Normal View History

import uPlot, { Cursor, Band, Hooks, Select } from 'uplot';
import { defaultsDeep } from 'lodash';
import { PlotConfig, TooltipInterpolator } from '../types';
import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
import { AxisPlacement } from '../config';
import {
DataFrame,
DefaultTimeZone,
EventBus,
getTimeZoneInfo,
GrafanaTheme2,
TimeRange,
TimeZone,
} from '@grafana/data';
import { pluginLog } from '../utils';
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
export class UPlotConfigBuilder {
private series: UPlotSeriesBuilder[] = [];
private axes: Record<string, UPlotAxisBuilder> = {};
private scales: UPlotScaleBuilder[] = [];
private bands: Band[] = [];
private cursor: Cursor | undefined;
private isStacking = false;
2021-05-05 16:44:31 +08:00
private select: uPlot.Select | undefined;
private hasLeftAxis = false;
private hasBottomAxis = false;
private hooks: Hooks.Arrays = {};
private tz: string | undefined = undefined;
private sync = false;
// to prevent more than one threshold per scale
private thresholds: Record<string, UPlotThresholdOptions> = {};
/**
* Custom handler for closest datapoint and series lookup. Technicaly returns uPlots setCursor hook
* that sets tooltips state.
*/
tooltipInterpolator: TooltipInterpolator | undefined = undefined;
2021-05-05 16:44:31 +08:00
constructor(timeZone: TimeZone = DefaultTimeZone) {
this.tz = getTimeZoneInfo(timeZone, Date.now())?.ianaName;
}
GraphNG - shared cursor (#33433) * Initial work * WIP add cursor in debug panel * shared cursor.sync filter * explicit uplot events * explicit uplot events * uplot events * uplot events * depend on master uplot * sync sync sync * Fix merge * Get rid of PlotSyncContext and sync tooltip positions * make sync optional * Improve shared tooltip positioning * Plugins: add level and signature badges to plugin details page (#33553) * feat(grafana-ui): badge can accept react node for text, add shield-exclamation to icons * feat(plugins): add PluginSignatureType type * feat(pluginpage): introduce PluginSignatureDetailsBadge. Fix sidebar icon margin * feat(pluginlistpage): update filterinput placeholder, introduce filter by plugin type * Variables: Removes the never refresh option (#33533) * Variables: Removes the never refresh option * Tests: fixes DashboardModel repeat tests * Tests: fixs snapshots * Tests: fixes processVariable test * Tests: fixes DashboardModel tests * PageLayout: Fixes max-width breakpoint so that it triggers only when there is room for margin+ (#33558) * Alerting: Remove datasource (name) from migration (#33544) no longer needed as of https://github.com/grafana/grafana/pull/33416 for https://github.com/grafana/alerting-squad/issues/126 * Library panels: Adds description to library panels tab (#33428) * CodeOwners: Set owners of unified alerting migration (#33571) * ButtonSelect: updates component with the new theme model (#33565) * EmptySearchResult: updates component with the new theme model (#33573) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design (#33575) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design * Fixed font weight * Removed comment * Update * gitignore: Ignore files for accesscontrol provisioning (#33577) * Alerting/metrics (#33547) * moves alerting metrics to their own pkg * adds grafana_alerting_alerts (by state) metric * alerts_received_{total,invalid} * embed alertmanager alerting struct in ng metrics & remove duplicated notification metrics (already embed alertmanager notifier metrics) * use silence metrics from alertmanager lib * fix - manager has metrics * updates ngalert tests * comment lint Signed-off-by: Owen Diehl <ow.diehl@gmail.com> * cleaner prom registry code * removes ngalert global metrics * new registry use in all tests * ngalert metrics impl service, hack testinfra code to prevent duplicate metric registrations * nilmetrics unexported * Add note to Snapshot API doc to specify that user has to provide the entire dashboard model (#33572) * Added note as suggested by Macus E. * Update docs/sources/http_api/snapshot.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Alerting: backend "ng" code cleanup (#33578) * AlertMigration: remove alert_rule UID db check (#33568) do not believe this is needed due to uniqueness promised by shortid lib since there is no provisioning yet. https://github.com/teris-io/shortid * Live: persisting last message in cache for broadcast scope (#32938) * Alerting: Load annotations from rule into State cache (#33542) for https://github.com/grafana/alerting-squad/issues/127 * add template for dashboard url parameters (#33549) * Update dashboard-links.md parameters with plain text like `var-something=value` can make confusion. template it to clarify . * describe way for template link. * AlertingMigration: Create alert_rule_version entry (#33585) Create the alert rule version entry during the migration so it is consistent with rules created via api. for https://github.com/grafana/alerting-squad/issues/123 * Build: Fix with cleanup call maybe? (#33590) * add selector for code editor (#33554) * broadcast over eventBus * broadcasting to eventbus (but not useing it yet) * merge master * moved to context * fix yarn.lock * update snapshot * Fix direct state mutation * Persist location state on partial updates * GraphNG- use getStream rather than subscribe * Sync LegacyGraphHoverEvent with GraphNG * Chenge plotRef signature * use subscription * subscription * one fewer file * Update types * Remove unnecessary filtering * Disable cursor sync when in edit mode * GraphNG - bring back logging Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> Co-authored-by: Uchechukwu Obasi <obasiuche62@gmail.com> Co-authored-by: gamab <gamab@users.noreply.github.com> Co-authored-by: Owen Diehl <ow.diehl@gmail.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Alexander Emelin <frvzmb@gmail.com> Co-authored-by: Nagle Zhang <nagle.zhang@sap.com> Co-authored-by: Erik Sundell <erik.sundell@grafana.com>
2021-05-10 20:24:23 +08:00
// Exposed to let the container know the primary scale keys
scaleKeys: [string, string] = ['', ''];
addHook<T extends keyof Hooks.Defs>(type: T, hook: Hooks.Defs[T]) {
pluginLog('UPlotConfigBuilder', false, 'addHook', type);
if (!this.hooks[type]) {
this.hooks[type] = [];
}
this.hooks[type]!.push(hook as any);
}
addThresholds(options: UPlotThresholdOptions) {
if (!this.thresholds[options.scaleKey]) {
this.thresholds[options.scaleKey] = options;
this.addHook('drawClear', getThresholdsDrawHook(options));
}
}
addAxis(props: AxisProps) {
props.placement = props.placement ?? AxisPlacement.Auto;
if (this.axes[props.scaleKey]) {
this.axes[props.scaleKey].merge(props);
return;
}
// Handle auto placement logic
if (props.placement === AxisPlacement.Auto) {
props.placement = this.hasLeftAxis ? AxisPlacement.Right : AxisPlacement.Left;
}
switch (props.placement) {
case AxisPlacement.Left:
this.hasLeftAxis = true;
break;
case AxisPlacement.Bottom:
this.hasBottomAxis = true;
break;
}
if (props.placement === AxisPlacement.Hidden) {
props.show = false;
props.size = 0;
}
this.axes[props.scaleKey] = new UPlotAxisBuilder(props);
}
getAxisPlacement(scaleKey: string): AxisPlacement {
const axis = this.axes[scaleKey];
return axis?.props.placement! ?? AxisPlacement.Left;
}
setCursor(cursor?: Cursor) {
GraphNG - shared cursor (#33433) * Initial work * WIP add cursor in debug panel * shared cursor.sync filter * explicit uplot events * explicit uplot events * uplot events * uplot events * depend on master uplot * sync sync sync * Fix merge * Get rid of PlotSyncContext and sync tooltip positions * make sync optional * Improve shared tooltip positioning * Plugins: add level and signature badges to plugin details page (#33553) * feat(grafana-ui): badge can accept react node for text, add shield-exclamation to icons * feat(plugins): add PluginSignatureType type * feat(pluginpage): introduce PluginSignatureDetailsBadge. Fix sidebar icon margin * feat(pluginlistpage): update filterinput placeholder, introduce filter by plugin type * Variables: Removes the never refresh option (#33533) * Variables: Removes the never refresh option * Tests: fixes DashboardModel repeat tests * Tests: fixs snapshots * Tests: fixes processVariable test * Tests: fixes DashboardModel tests * PageLayout: Fixes max-width breakpoint so that it triggers only when there is room for margin+ (#33558) * Alerting: Remove datasource (name) from migration (#33544) no longer needed as of https://github.com/grafana/grafana/pull/33416 for https://github.com/grafana/alerting-squad/issues/126 * Library panels: Adds description to library panels tab (#33428) * CodeOwners: Set owners of unified alerting migration (#33571) * ButtonSelect: updates component with the new theme model (#33565) * EmptySearchResult: updates component with the new theme model (#33573) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design (#33575) * DashboardSettings: Slight design tweak to fix page toolbar padding and align design * Fixed font weight * Removed comment * Update * gitignore: Ignore files for accesscontrol provisioning (#33577) * Alerting/metrics (#33547) * moves alerting metrics to their own pkg * adds grafana_alerting_alerts (by state) metric * alerts_received_{total,invalid} * embed alertmanager alerting struct in ng metrics & remove duplicated notification metrics (already embed alertmanager notifier metrics) * use silence metrics from alertmanager lib * fix - manager has metrics * updates ngalert tests * comment lint Signed-off-by: Owen Diehl <ow.diehl@gmail.com> * cleaner prom registry code * removes ngalert global metrics * new registry use in all tests * ngalert metrics impl service, hack testinfra code to prevent duplicate metric registrations * nilmetrics unexported * Add note to Snapshot API doc to specify that user has to provide the entire dashboard model (#33572) * Added note as suggested by Macus E. * Update docs/sources/http_api/snapshot.md Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> * Alerting: backend "ng" code cleanup (#33578) * AlertMigration: remove alert_rule UID db check (#33568) do not believe this is needed due to uniqueness promised by shortid lib since there is no provisioning yet. https://github.com/teris-io/shortid * Live: persisting last message in cache for broadcast scope (#32938) * Alerting: Load annotations from rule into State cache (#33542) for https://github.com/grafana/alerting-squad/issues/127 * add template for dashboard url parameters (#33549) * Update dashboard-links.md parameters with plain text like `var-something=value` can make confusion. template it to clarify . * describe way for template link. * AlertingMigration: Create alert_rule_version entry (#33585) Create the alert rule version entry during the migration so it is consistent with rules created via api. for https://github.com/grafana/alerting-squad/issues/123 * Build: Fix with cleanup call maybe? (#33590) * add selector for code editor (#33554) * broadcast over eventBus * broadcasting to eventbus (but not useing it yet) * merge master * moved to context * fix yarn.lock * update snapshot * Fix direct state mutation * Persist location state on partial updates * GraphNG- use getStream rather than subscribe * Sync LegacyGraphHoverEvent with GraphNG * Chenge plotRef signature * use subscription * subscription * one fewer file * Update types * Remove unnecessary filtering * Disable cursor sync when in edit mode * GraphNG - bring back logging Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> Co-authored-by: Uchechukwu Obasi <obasiuche62@gmail.com> Co-authored-by: gamab <gamab@users.noreply.github.com> Co-authored-by: Owen Diehl <ow.diehl@gmail.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Ursula Kallio <73951760+osg-grafana@users.noreply.github.com> Co-authored-by: Alexander Emelin <frvzmb@gmail.com> Co-authored-by: Nagle Zhang <nagle.zhang@sap.com> Co-authored-by: Erik Sundell <erik.sundell@grafana.com>
2021-05-10 20:24:23 +08:00
this.cursor = { ...this.cursor, ...cursor };
}
2021-05-05 16:44:31 +08:00
setSelect(select: Select) {
this.select = select;
}
setStacking(enabled = true) {
this.isStacking = enabled;
}
addSeries(props: SeriesProps) {
this.series.push(new UPlotSeriesBuilder(props));
}
getSeries() {
return this.series;
}
/** Add or update the scale with the scale key */
addScale(props: ScaleProps) {
const current = this.scales.find((v) => v.props.scaleKey === props.scaleKey);
if (current) {
current.merge(props);
return;
}
this.scales.push(new UPlotScaleBuilder(props));
}
addBand(band: Band) {
this.bands.push(band);
}
setTooltipInterpolator(interpolator: TooltipInterpolator) {
this.tooltipInterpolator = interpolator;
}
setSync() {
this.sync = true;
}
hasSync() {
return this.sync;
}
getConfig() {
const config: PlotConfig = { series: [{}] };
config.axes = this.ensureNonOverlappingAxes(Object.values(this.axes)).map((a) => a.getConfig());
config.series = [...config.series, ...this.series.map((s) => s.getConfig())];
config.scales = this.scales.reduce((acc, s) => {
return { ...acc, ...s.getConfig() };
}, {});
2020-12-12 03:01:55 +08:00
config.hooks = this.hooks;
/* @ts-ignore */
// uPlot types don't export the Select interface prior to 1.6.4
config.select = this.select;
2020-12-12 03:01:55 +08:00
config.cursor = this.cursor || {};
config.tzDate = this.tzDate;
if (this.isStacking) {
// Let uPlot handle bands and fills
config.bands = this.bands;
} else {
// When fillBelowTo option enabled, handle series bands fill manually
if (this.bands?.length) {
config.bands = this.bands;
const keepFill = new Set<number>();
for (const b of config.bands) {
keepFill.add(b.series[0]);
}
for (let i = 1; i < config.series.length; i++) {
if (!keepFill.has(i)) {
config.series[i].fill = undefined;
}
}
}
}
const cursorDefaults: Cursor = {
// prevent client-side zoom from triggering at the end of a selection
drag: { setScale: false },
points: {
/*@ts-ignore*/
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2,
/*@ts-ignore*/
width: (u, seriesIdx, size) => size / 4,
/*@ts-ignore*/
stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '80',
/*@ts-ignore*/
fill: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx),
},
2020-12-17 23:57:11 +08:00
focus: {
prox: 30,
},
};
defaultsDeep(config.cursor, cursorDefaults);
2020-12-12 03:01:55 +08:00
return config;
}
private ensureNonOverlappingAxes(axes: UPlotAxisBuilder[]): UPlotAxisBuilder[] {
for (const axis of axes) {
if (axis.props.placement === AxisPlacement.Right && this.hasLeftAxis) {
axis.props.grid = false;
}
if (axis.props.placement === AxisPlacement.Top && this.hasBottomAxis) {
axis.props.grid = false;
}
}
return axes;
}
private tzDate = (ts: number) => {
let date = new Date(ts);
return this.tz ? uPlot.tzDate(date, this.tz) : date;
};
}
/** @alpha */
type UPlotConfigPrepOpts<T extends Record<string, any> = {}> = {
frame: DataFrame;
theme: GrafanaTheme2;
timeZone: TimeZone;
getTimeRange: () => TimeRange;
eventBus: EventBus;
} & T;
/** @alpha */
export type UPlotConfigPrepFn<T extends {} = {}> = (opts: UPlotConfigPrepOpts<T>) => UPlotConfigBuilder;