Annotations: VizActions support in annotation tooltips (#112141)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Galen Kistler
2025-10-21 20:52:08 -05:00
committed by GitHub
co-authored by Leon Sorokin
parent 5aef57af73
commit fcf7aa5e2f
9 changed files with 51 additions and 20 deletions
@@ -321,6 +321,7 @@ export const CandlestickPanel = ({
/>
)}
<AnnotationsPlugin2
replaceVariables={replaceVariables}
annotations={data.annotations ?? []}
config={uplotConfig}
timeZone={timeZone}
@@ -230,6 +230,7 @@ export const HeatmapPanel = ({
/>
)}
<AnnotationsPlugin2
replaceVariables={replaceVariables}
annotations={data.annotations ?? []}
config={builder}
timeZone={timeZone}
@@ -151,6 +151,7 @@ export const StateTimelinePanel = ({
)}
{alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && (
<AnnotationsPlugin2
replaceVariables={replaceVariables}
annotations={data.annotations ?? []}
config={builder}
timeZone={timeZone}
@@ -164,6 +164,7 @@ export const StatusHistoryPanel = ({
)}
{alignedFrame.fields[0].config.custom?.axisPlacement !== AxisPlacement.Hidden && (
<AnnotationsPlugin2
replaceVariables={replaceVariables}
annotations={data.annotations ?? []}
config={builder}
timeZone={timeZone}
@@ -22,8 +22,6 @@ export const getDataLinks = (field: Field, rowIdx: number) => {
return links;
};
export const getAllFrameActions = (dataFrame: DataFrame) => {};
export const getFieldActions = (
dataFrame: DataFrame,
field: Field,
@@ -31,19 +29,22 @@ export const getFieldActions = (
rowIndex: number
) => {
const actions: Array<ActionModel<Field>> = [];
const actionLookup = new Set<string>();
const actionsModel = getActions(dataFrame, field, field.state!.scopedVars!, replaceVars, field.config.actions ?? [], {
valueRowIndex: rowIndex,
});
if (field.state?.scopedVars) {
const actionLookup = new Set<string>();
actionsModel.forEach((action) => {
const key = `${action.title}`;
if (!actionLookup.has(key)) {
actions.push(action);
actionLookup.add(key);
}
});
const actionsModel = getActions(dataFrame, field, field.state.scopedVars, replaceVars, field.config.actions ?? [], {
valueRowIndex: rowIndex,
});
actionsModel.forEach((action) => {
const key = `${action.title}`;
if (!actionLookup.has(key)) {
actions.push(action);
actionLookup.add(key);
}
});
}
return actions;
};
@@ -191,6 +191,7 @@ export const TimeSeriesPanel = ({
{!isVerticallyOriented && (
<>
<AnnotationsPlugin2
replaceVariables={replaceVariables}
annotations={data.annotations ?? []}
config={uplotConfig}
timeZone={timeZone}
@@ -5,9 +5,16 @@ import { createPortal } from 'react-dom';
import tinycolor from 'tinycolor2';
import uPlot from 'uplot';
import { arrayToDataFrame, colorManipulator, DataFrame, DataTopic } from '@grafana/data';
import { arrayToDataFrame, colorManipulator, DataFrame, DataTopic, InterpolateFunction } from '@grafana/data';
import { TimeZone } from '@grafana/schema';
import { DEFAULT_ANNOTATION_COLOR, getPortalContainer, UPlotConfigBuilder, useStyles2, useTheme2 } from '@grafana/ui';
import {
DEFAULT_ANNOTATION_COLOR,
getPortalContainer,
UPlotConfigBuilder,
usePanelContext,
useStyles2,
useTheme2,
} from '@grafana/ui';
import { AnnotationMarker2 } from './annotations2/AnnotationMarker2';
@@ -24,6 +31,7 @@ interface AnnotationsPluginProps {
newRange: TimeRange2 | null;
setNewRange: (newRage: TimeRange2 | null) => void;
canvasRegionRendering?: boolean;
replaceVariables: InterpolateFunction;
}
// TODO: batch by color, use Path2D objects
@@ -62,6 +70,7 @@ export const AnnotationsPlugin2 = ({
config,
newRange,
setNewRange,
replaceVariables,
canvasRegionRendering = true,
}: AnnotationsPluginProps) => {
const [plot, setPlot] = useState<uPlot>();
@@ -73,6 +82,9 @@ export const AnnotationsPlugin2 = ({
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
const { canExecuteActions } = usePanelContext();
const userCanExecuteActions = canExecuteActions?.() ?? false;
const annos = useMemo(() => {
let annos = annotations.filter(
(frame) => frame.name !== 'exemplar' && frame.length > 0 && frame.fields.some((f) => f.name === 'time')
@@ -258,6 +270,8 @@ export const AnnotationsPlugin2 = ({
key={`${frameIdx}:${i}`}
exitWipEdit={isWip ? exitWipEdit : null}
portalRoot={portalRoot}
canExecuteActions={userCanExecuteActions}
replaceVariables={replaceVariables}
/>
);
}
@@ -5,11 +5,11 @@ import { useState } from 'react';
import * as React from 'react';
import { createPortal } from 'react-dom';
import { DataFrame, GrafanaTheme2, LinkModel } from '@grafana/data';
import { ActionModel, DataFrame, GrafanaTheme2, InterpolateFunction, LinkModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { TimeZone } from '@grafana/schema';
import { floatingUtils, useStyles2 } from '@grafana/ui';
import { getDataLinks } from 'app/plugins/panel/status-history/utils';
import { getDataLinks, getFieldActions } from 'app/plugins/panel/status-history/utils';
import { AnnotationEditor2 } from './AnnotationEditor2';
import { AnnotationTooltip2 } from './AnnotationTooltip2';
@@ -23,6 +23,8 @@ interface AnnoBoxProps {
timeZone: TimeZone;
exitWipEdit?: null | (() => void);
portalRoot: HTMLElement;
canExecuteActions: boolean;
replaceVariables: InterpolateFunction;
}
const STATE_DEFAULT = 0;
@@ -38,6 +40,8 @@ export const AnnotationMarker2 = ({
exitWipEdit,
timeZone,
portalRoot,
replaceVariables,
canExecuteActions,
}: AnnoBoxProps) => {
const styles = useStyles2(getStyles);
const placement = 'bottom';
@@ -52,10 +56,15 @@ export const AnnotationMarker2 = ({
});
const links: LinkModel[] = [];
const actions: ActionModel[] = [];
if (STATE_HOVERED) {
frame.fields.forEach((field) => {
links.push(...getDataLinks(field, annoIdx));
if (canExecuteActions) {
actions.push(...getFieldActions(frame, field, replaceVariables, annoIdx));
}
});
}
@@ -67,6 +76,7 @@ export const AnnotationMarker2 = ({
timeZone={timeZone}
onEdit={() => setState(STATE_EDITING)}
links={links}
actions={actions}
/>
) : state === STATE_EDITING ? (
<AnnotationEditor2
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import * as React from 'react';
import { GrafanaTheme2, dateTimeFormat, systemDateFormats, textUtil, LinkModel } from '@grafana/data';
import { GrafanaTheme2, dateTimeFormat, systemDateFormats, textUtil, LinkModel, ActionModel } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Stack, IconButton, Tag, usePanelContext, useStyles2 } from '@grafana/ui';
import { VizTooltipFooter } from '@grafana/ui/internal';
@@ -13,11 +13,12 @@ interface Props {
timeZone: string;
onEdit: () => void;
links?: LinkModel[];
actions?: ActionModel[];
}
const retFalse = () => false;
export const AnnotationTooltip2 = ({ annoVals, annoIdx, timeZone, onEdit, links = [] }: Props) => {
export const AnnotationTooltip2 = ({ annoVals, annoIdx, timeZone, onEdit, links = [], actions = [] }: Props) => {
const annoId = annoVals.id?.[annoIdx];
const styles = useStyles2(getStyles);
@@ -110,7 +111,7 @@ export const AnnotationTooltip2 = ({ annoVals, annoIdx, timeZone, onEdit, links
</div>
</div>
{links.length > 0 && <VizTooltipFooter dataLinks={links} />}
{(links.length > 0 || actions.length > 0) && <VizTooltipFooter dataLinks={links} actions={actions} />}
</div>
);
};