diff --git a/src/AppDataTrail/DataTrail.tsx b/src/AppDataTrail/DataTrail.tsx index 43798a657..b8dff7844 100644 --- a/src/AppDataTrail/DataTrail.tsx +++ b/src/AppDataTrail/DataTrail.tsx @@ -53,6 +53,7 @@ import { GmdVizPanel } from 'shared/GmdVizPanel/GmdVizPanel'; import { logger } from 'shared/logger/logger'; import { resetYAxisSync } from '../MetricScene/Breakdown/MetricLabelsList/behaviors/syncYAxis'; +import { type KgEntityConfig } from '../MetricScene/kgAnnotations'; import { MetricScene } from '../MetricScene/MetricScene'; import { type PanelDataRequestPayload } from '../shared/GmdVizPanel/components/addToDashboard/addToDashboard'; import { MetricSelectedEvent, trailDS, VAR_DATASOURCE, VAR_FILTERS } from '../shared/shared'; @@ -71,6 +72,7 @@ export interface DataTrailState extends SceneObjectState { embedded?: boolean; embeddedMini?: boolean; // Mini embedded mode for tooltip preview navigation controls: SceneObject[]; + kgEntityConfig?: KgEntityConfig; createdAt: number; // wingman @@ -181,7 +183,7 @@ export class DataTrail extends SceneObjectBase implements SceneO this.setState({ metric, - topScene: metric ? new MetricScene({ metric }) : new MetricsReducer(), + topScene: metric ? new MetricScene({ metric, kgEntityConfig: this.state.kgEntityConfig }) : new MetricsReducer(), controls, }); } diff --git a/src/MetricScene/MetricGraphScene.tsx b/src/MetricScene/MetricGraphScene.tsx index 19e296b18..f92b16540 100644 --- a/src/MetricScene/MetricGraphScene.tsx +++ b/src/MetricScene/MetricGraphScene.tsx @@ -28,6 +28,7 @@ import { QUERY_RESOLUTION } from 'shared/GmdVizPanel/config/query-resolutions'; import { GmdVizPanel } from 'shared/GmdVizPanel/GmdVizPanel'; import { isClassicHistogramMetric } from 'shared/GmdVizPanel/matchers/isClassicHistogramMetric'; +import { buildKgAnnotationsLayer, type KgEntityConfig } from './kgAnnotations'; import { MetricActionBar } from './MetricActionBar'; import { PanelMenu } from './PanelMenu/PanelMenu'; import { buildMiniBreakdownNavigationUrl } from '../exposedComponents/MiniBreakdown/buildNavigationUrl'; @@ -47,11 +48,18 @@ interface MetricGraphSceneState extends SceneObjectState { } export class MetricGraphScene extends SceneObjectBase { - public constructor({ metric }: { metric: MetricGraphSceneState['metric'] }) { + public constructor({ + metric, + kgEntityConfig, + }: { + metric: MetricGraphSceneState['metric']; + kgEntityConfig?: KgEntityConfig; + }) { super({ metric, topView: new SceneFlexLayout({ direction: 'column', + ...(kgEntityConfig ? { $data: buildKgAnnotationsLayer(kgEntityConfig) } : {}), $behaviors: [new behaviors.CursorSync({ key: 'metricCrosshairSync', sync: DashboardCursorSync.Crosshair })], children: [ new SceneFlexItem({ diff --git a/src/MetricScene/MetricScene.tsx b/src/MetricScene/MetricScene.tsx index ff1b18c96..f0d8efd82 100644 --- a/src/MetricScene/MetricScene.tsx +++ b/src/MetricScene/MetricScene.tsx @@ -18,6 +18,7 @@ import React, { useEffect } from 'react'; import { RefreshMetricsEvent, VAR_FILTERS, VAR_METRIC, type MakeOptional } from '../shared/shared'; import { GroupByVariable } from './Breakdown/GroupByVariable'; import { EventActionViewDataLoadComplete } from './EventActionViewDataLoadComplete'; +import { type KgEntityConfig } from './kgAnnotations'; import { actionViews, defaultActionView, getActionViewsDefinitions, type ActionViewType } from './MetricActionBar'; import { MetricGraphScene } from './MetricGraphScene'; import { @@ -30,6 +31,7 @@ import { RelatedLogsScene } from './RelatedLogs/RelatedLogsScene'; interface MetricSceneState extends SceneObjectState { body: MetricGraphScene; metric: string; + kgEntityConfig?: KgEntityConfig; actionView?: ActionViewType; relatedLogsCount?: number; isQueryResultsAvailable?: boolean; @@ -58,7 +60,7 @@ export class MetricScene extends SceneObjectBase { public constructor(state: MakeOptional) { super({ $variables: state.$variables ?? getVariableSet(state.metric), - body: state.body ?? new MetricGraphScene({ metric: state.metric }), + body: state.body ?? new MetricGraphScene({ metric: state.metric, kgEntityConfig: state.kgEntityConfig }), ...state, }); diff --git a/src/MetricScene/kgAnnotations.ts b/src/MetricScene/kgAnnotations.ts new file mode 100644 index 000000000..a00eeb79f --- /dev/null +++ b/src/MetricScene/kgAnnotations.ts @@ -0,0 +1,63 @@ +import { dataLayers, SceneDataLayerSet } from '@grafana/scenes'; +import { type DataQuery } from '@grafana/schema'; + +export type KgEntityScope = { + env?: string; + site?: string; + namespace?: string; +}; + +export type KgEntityConfig = { + datasourceUid: string; + entityType: string; + entityName: string; + entityScope?: KgEntityScope; +}; + +export function buildKgAnnotationsLayer({ + datasourceUid, + entityType, + entityName, + entityScope, +}: KgEntityConfig): SceneDataLayerSet { + return new SceneDataLayerSet({ + layers: [ + new dataLayers.AnnotationsDataLayer({ + name: 'Asserts Insights', + isEnabled: true, + query: { + name: 'Asserts Insights', + enable: true, + iconColor: 'red', + datasource: { type: 'grafana-knowledgegraph-datasource', uid: datasourceUid }, + target: { + refId: 'kgAnnotations', + queryType: 'annotations', + queryMode: 'advanced', + advancedQuery: { + filterCriteria: [ + { + entityType, + propertyMatchers: [{ id: -1, name: 'name', op: '=', value: entityName, type: 'String' }], + connectToEntityTypes: [], + havingAssertion: false, + }, + ], + ...(entityScope + ? { + scopeCriteria: { + nameAndValues: { + env: entityScope.env ? [entityScope.env] : undefined, + site: entityScope.site ? [entityScope.site] : undefined, + namespace: entityScope.namespace ? [entityScope.namespace] : undefined, + }, + }, + } + : {}), + }, + } as unknown as DataQuery, + }, + }), + ], + }); +} diff --git a/src/exposedComponents/SourceMetrics/SourceMetrics.tsx b/src/exposedComponents/SourceMetrics/SourceMetrics.tsx index 697fb2cf4..9d1f31825 100644 --- a/src/exposedComponents/SourceMetrics/SourceMetrics.tsx +++ b/src/exposedComponents/SourceMetrics/SourceMetrics.tsx @@ -1,6 +1,6 @@ import { type AdHocVariableFilter, type DataSourceApi } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { ErrorView } from 'App/ErrorView'; import { Trail } from 'App/Routes'; @@ -15,6 +15,7 @@ import { labelMatcherToAdHocFilter } from 'shared/utils/utils.variables'; import { FilterGroupByAssertsLabelsBehavior } from './behaviors/FilterGroupByAssertsLabelsBehavior'; import { HistogramPercentilesDefaultBehavior } from './behaviors/HistogramPercentilesDefaultBehavior'; import { parsePromQLQuery } from '../../extensions/links'; +import { type KgEntityConfig, type KgEntityScope } from '../../MetricScene/kgAnnotations'; import { type PromQLLabelMatcher } from '../../shared/utils/utils.promql'; import { toSceneTimeRange } from '../../shared/utils/utils.timerange'; @@ -108,12 +109,23 @@ export interface SourceMetricsProps { initialEnd: string | number; dataSource: DataSourceApi; sourceMetrics?: SourceMetrics; + kgDatasourceUid?: string; + entityType?: string; + entityName?: string; + entityScope?: KgEntityScope; } -const KnowledgeGraphSourceMetrics = (props: SourceMetricsProps) => { +const KnowledgeGraphSourceMetrics = ({ kgDatasourceUid, entityType, entityName, entityScope, ...props }: SourceMetricsProps) => { const [error] = useCatchExceptions(); const initRef = useRef(false); + const kgEntityConfig = useMemo(() => { + if (!kgDatasourceUid || !entityType || !entityName) { + return undefined; + } + return { datasourceUid: kgDatasourceUid, entityType, entityName, entityScope }; + }, [kgDatasourceUid, entityType, entityName, entityScope]); + useEffect(() => { if (!initRef.current) { initRef.current = true; @@ -177,6 +189,7 @@ const KnowledgeGraphSourceMetrics = (props: SourceMetricsProps) => { $timeRange: toSceneTimeRange(props.initialStart, props.initialEnd), embedded: true, $behaviors: [new FilterGroupByAssertsLabelsBehavior({ metric }), new HistogramPercentilesDefaultBehavior()], + ...(kgEntityConfig ? { kgEntityConfig } : {}), }); return ( diff --git a/src/shared/GmdVizPanel/types/timeseries/buildTimeseriesPanel.ts b/src/shared/GmdVizPanel/types/timeseries/buildTimeseriesPanel.ts index 1598260a0..e7b98d242 100644 --- a/src/shared/GmdVizPanel/types/timeseries/buildTimeseriesPanel.ts +++ b/src/shared/GmdVizPanel/types/timeseries/buildTimeseriesPanel.ts @@ -39,6 +39,7 @@ export function buildTimeseriesPanel(options: BuildVizPanelOptions): VizPanel { .setData($data) .setUnit(unit) .setOption('legend', panelConfig.legend || { showLegend: true, placement: 'bottom' as LegendPlacement }) + .setOption('annotations', { multiLane: true }) .setCustomFieldConfig('fillOpacity', 9) .setBehaviors([ extremeValueFilterBehavior,