From 9887a4184d3a8a62a2f398d9e83fe171b35848ec Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 23 Sep 2025 16:08:24 +0300 Subject: [PATCH] Annotations: Add frontend api wrapper (#111452) --- .../alerting/unified/api/annotations.test.ts | 4 ++ .../alerting/unified/api/annotations.ts | 8 +-- public/app/features/annotations/api.ts | 61 ++++++++++++++----- .../scene/setDashboardPanelContext.ts | 8 +-- .../dashboard/dashgrid/PanelStateWrapper.tsx | 8 +-- .../components/AnnotationQueryEditor.tsx | 4 +- .../plugins/datasource/grafana/datasource.ts | 8 +-- .../annotations2/AnnotationEditor2.tsx | 4 +- 8 files changed, 69 insertions(+), 36 deletions(-) diff --git a/public/app/features/alerting/unified/api/annotations.test.ts b/public/app/features/alerting/unified/api/annotations.test.ts index f80d7e8f22c..4854ec87d19 100644 --- a/public/app/features/alerting/unified/api/annotations.test.ts +++ b/public/app/features/alerting/unified/api/annotations.test.ts @@ -10,6 +10,10 @@ const get = jest.fn(() => { }); jest.mock('@grafana/runtime', () => ({ + config: { + namespace: 'default', + featureToggles: {}, + }, getBackendSrv: () => ({ get, }), diff --git a/public/app/features/alerting/unified/api/annotations.ts b/public/app/features/alerting/unified/api/annotations.ts index 2e0d577ffbf..56002e6b40d 100644 --- a/public/app/features/alerting/unified/api/annotations.ts +++ b/public/app/features/alerting/unified/api/annotations.ts @@ -1,11 +1,9 @@ -import { getBackendSrv } from '@grafana/runtime'; +import { annotationServer } from 'app/features/annotations/api'; import { StateHistoryItem } from 'app/types/unified-alerting'; export function fetchAnnotations(alertUID: string): Promise { - return getBackendSrv() - .get('/api/annotations', { - alertUID, - }) + return annotationServer() + .forAlert(alertUID) .then((result) => { return result?.sort(sortStateHistory); }); diff --git a/public/app/features/annotations/api.ts b/public/app/features/annotations/api.ts index ebb8e683650..4975f3e1d49 100644 --- a/public/app/features/annotations/api.ts +++ b/public/app/features/annotations/api.ts @@ -1,24 +1,57 @@ -import { AnnotationEvent } from '@grafana/data'; +import { AnnotationEvent, DataFrame, toDataFrame } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; +import { StateHistoryItem } from 'app/types/unified-alerting'; import { AnnotationTagsResponse } from './types'; -export function saveAnnotation(annotation: AnnotationEvent) { - return getBackendSrv().post('/api/annotations', annotation); +export interface AnnotationServer { + query(params: Record, requestId: string): Promise; + forAlert(alertUID: string): Promise; + save(annotation: AnnotationEvent): Promise; + update(annotation: AnnotationEvent): Promise; + delete(annotation: AnnotationEvent): Promise; + tags(): Promise>; } -export function updateAnnotation(annotation: AnnotationEvent) { - return getBackendSrv().put(`/api/annotations/${annotation.id}`, annotation); +class LegacyAnnotationServer implements AnnotationServer { + query(params: unknown, requestId: string): Promise { + return getBackendSrv() + .get('/api/annotations', params, requestId) + .then((v) => toDataFrame(v)); + } + + forAlert(alertUID: string) { + return getBackendSrv().get('/api/annotations', { + alertUID, + }); + } + + save(annotation: AnnotationEvent) { + return getBackendSrv().post('/api/annotations', annotation); + } + + update(annotation: AnnotationEvent) { + return getBackendSrv().put(`/api/annotations/${annotation.id}`, annotation); + } + + delete(annotation: AnnotationEvent) { + return getBackendSrv().delete(`/api/annotations/${annotation.id}`); + } + + async tags() { + const response = await getBackendSrv().get('/api/annotations/tags'); + return response.result.tags.map(({ tag, count }) => ({ + term: tag, + count, + })); + } } -export function deleteAnnotation(annotation: AnnotationEvent) { - return getBackendSrv().delete(`/api/annotations/${annotation.id}`); -} +let instance: AnnotationServer | null = null; -export async function getAnnotationTags() { - const response: AnnotationTagsResponse = await getBackendSrv().get('/api/annotations/tags'); - return response.result.tags.map(({ tag, count }) => ({ - term: tag, - count, - })); +export function annotationServer(): AnnotationServer { + if (!instance) { + instance = new LegacyAnnotationServer(); + } + return instance; } diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts index 2eeeea9805c..62f693f1d59 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts @@ -3,7 +3,7 @@ import { config, getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable, dataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes'; import { DataSourceRef } from '@grafana/schema'; import { AdHocFilterItem, PanelContext } from '@grafana/ui'; -import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api'; +import { annotationServer } from 'app/features/annotations/api'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; @@ -84,7 +84,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte text: event.description, }; - await saveAnnotation(anno); + await annotationServer().save(anno); reRunBuiltInAnnotationsLayer(dashboard); @@ -106,7 +106,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte text: event.description, }; - await updateAnnotation(anno); + await annotationServer().update(anno); reRunBuiltInAnnotationsLayer(dashboard); @@ -114,7 +114,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte }; context.onAnnotationDelete = async (id: string) => { - await deleteAnnotation({ id }); + await annotationServer().delete({ id }); reRunBuiltInAnnotationsLayer(getDashboardSceneFor(vizPanel)); diff --git a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx index 5215f6cec26..14fa037ee55 100644 --- a/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx +++ b/public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx @@ -35,6 +35,7 @@ import { import appEvents from 'app/core/app_events'; import config from 'app/core/config'; import { profiler } from 'app/core/profiler'; +import { annotationServer } from 'app/features/annotations/api'; import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { applyFilterFromTable } from 'app/features/variables/adhoc/actions'; @@ -43,7 +44,6 @@ import { changeSeriesColorConfigFactory } from 'app/plugins/panel/timeseries/ove import { dispatch } from 'app/store/store'; import { RenderEvent } from 'app/types/events'; -import { deleteAnnotation, saveAnnotation, updateAnnotation } from '../../annotations/api'; import { getDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; import { DashboardModel } from '../state/DashboardModel'; @@ -421,13 +421,13 @@ export class PanelStateWrapper extends PureComponent { tags: event.tags, text: event.description, }; - await saveAnnotation(anno); + await annotationServer().save(anno); getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() }); this.state.context.eventBus.publish(new AnnotationChangeEvent(anno)); }; onAnnotationDelete = async (id: string) => { - await deleteAnnotation({ id }); + await annotationServer().delete({ id }); getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() }); this.state.context.eventBus.publish(new AnnotationChangeEvent({ id })); }; @@ -444,7 +444,7 @@ export class PanelStateWrapper extends PureComponent { tags: event.tags, text: event.description, }; - await updateAnnotation(anno); + await annotationServer().update(anno); getDashboardQueryRunner().run({ dashboard: this.props.dashboard, range: this.timeSrv.timeRange() }); this.state.context.eventBus.publish(new AnnotationChangeEvent(anno)); diff --git a/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx index 2314ae6a2be..564b32d78c1 100644 --- a/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx @@ -6,7 +6,7 @@ import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Field, FieldSet, Select, Switch, useStyles2 } from '@grafana/ui'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; import { TimeRegionConfig } from 'app/core/utils/timeRegions'; -import { getAnnotationTags } from 'app/features/annotations/api'; +import { annotationServer } from 'app/features/annotations/api'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQueryType } from '../types'; @@ -146,7 +146,7 @@ export default function AnnotationQueryEditor({ query, onChange }: Props) { allowCustomValue inputId="grafana-annotations__tags" onChange={onTagsChange} - tagOptions={getAnnotationTags} + tagOptions={annotationServer().tags} tags={tags ?? []} /> diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 9c8af582ddb..98c579bcf5e 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -13,19 +13,18 @@ import { isValidLiveChannelAddress, MutableDataFrame, parseLiveChannelAddress, - toDataFrame, dataFrameFromJSON, LoadingState, } from '@grafana/data'; import { DataSourceWithBackend, - getBackendSrv, getDataSourceSrv, getGrafanaLiveSrv, getTemplateSrv, StreamingFrameOptions, } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; +import { annotationServer } from 'app/features/annotations/api'; import { migrateDatasourceNameToRef } from 'app/features/dashboard/state/DashboardMigrator'; import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; @@ -241,12 +240,11 @@ export class GrafanaDatasource extends DataSourceWithBackend { params.tags = tags; } - const annotations = await getBackendSrv().get( - '/api/annotations', + const df = await annotationServer().query( params, `grafana-data-source-annotations-${annotation.name}-${options.dashboard?.uid}` ); - return { data: [toDataFrame(annotations)] }; + return { data: [df] }; } testDatasource(): Promise { diff --git a/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx b/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx index 430fa011cd0..f9fae596e3c 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationEditor2.tsx @@ -8,7 +8,7 @@ import { Trans, t } from '@grafana/i18n'; import { Button, Field, Stack, TextArea, usePanelContext, useStyles2 } from '@grafana/ui'; import { Form } from 'app/core/components/Form/Form'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; -import { getAnnotationTags } from 'app/features/annotations/api'; +import { annotationServer } from 'app/features/annotations/api'; interface Props { annoVals: Record; @@ -109,7 +109,7 @@ export const AnnotationEditor2 = ({ annoVals, annoIdx, dismiss, timeZone, ...oth allowCustomValue placeholder={t('timeseries.annotation-editor2.placeholder-add-tags', 'Add tags')} onChange={onChange} - tagOptions={getAnnotationTags} + tagOptions={annotationServer().tags} tags={field.value} /> );