Skip to content

Commit

Permalink
Chore: Migrate #98900 from grafana/grafana (#53)
Browse files Browse the repository at this point in the history
* Chore: Migrate #98900 from grafana/grafana

* chore: use getExploreURL from @grafana/scenes

* fix: remove extra .

* fix: linter errors

* fix: linter error
  • Loading branch information
yangkb09 authored Jan 31, 2025
1 parent 2b2c0fa commit 16f7e53
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 29 deletions.
16 changes: 7 additions & 9 deletions src/Breakdown/LabelBreakdownScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { SortByScene, SortCriteriaChanged } from './SortByScene';
import { type BreakdownLayoutChangeCallback, type BreakdownLayoutType } from './types';
import { getLabelOptions } from './utils';
import { BreakdownAxisChangeEvent, yAxisSyncBehavior } from './yAxisSyncBehavior';
import { AddToExplorationButton } from '../MetricSelect/AddToExplorationsButton';
import { PanelMenu } from '../Menu/PanelMenu';
import { updateOtelJoinWithGroupLeft } from '../otel/util';
import { getSortByPreference } from '../services/store';
import { ALL_VARIABLE_VALUE } from '../services/variables';
Expand Down Expand Up @@ -474,10 +474,9 @@ export function buildAllLayout(
],
})
)
.setHeaderActions([
new SelectLabelAction({ labelName: String(option.value) }),
new AddToExplorationButton({ labelName: String(option.value) }),
])
.setHeaderActions([new SelectLabelAction({ labelName: String(option.value) })])
.setShowMenuAlways(true)
.setMenu(new PanelMenu({ labelName: String(option.value) }))
.setUnit(unit)
.setBehaviors([fixLegendForUnspecifiedLabelValueBehavior])
.build();
Expand Down Expand Up @@ -528,10 +527,9 @@ function buildNormalLayout(
.setTitle(getLabelValue(frame))
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
.setHeaderActions([
new AddToFiltersGraphAction({ frame }),
new AddToExplorationButton({ labelName: getLabelValue(frame) }),
])
.setHeaderActions([new AddToFiltersGraphAction({ frame })])
.setShowMenuAlways(true)
.setMenu(new PanelMenu({ labelName: getLabelValue(frame) }))
.setUnit(unit)
.build();

Expand Down
175 changes: 175 additions & 0 deletions src/Menu/PanelMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { type DataFrame, type PanelMenuItem } from '@grafana/data';
import { getPluginLinkExtensions } from '@grafana/runtime';
import {
getExploreURL,
sceneGraph,
SceneObjectBase,
VizPanel,
VizPanelMenu,
type SceneComponentProps,
type SceneObject,
type SceneObjectState,
} from '@grafana/scenes';
import React from 'react';

import { AddToExplorationButton, extensionPointId } from '../MetricSelect/AddToExplorationsButton';
import { getTrailFor } from '../utils';
import { getQueryRunnerFor } from '../utils/utils.queries';

const ADD_TO_INVESTIGATION_MENU_TEXT = 'Add to investigation';
const ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT = 'investigations_divider'; // Text won't be visible
const ADD_TO_INVESTIGATION_MENU_GROUP_TEXT = 'Investigations';

interface PanelMenuState extends SceneObjectState {
body?: VizPanelMenu;
frame?: DataFrame;
labelName?: string;
fieldName?: string;
addExplorationsLink?: boolean;
explorationsButton?: AddToExplorationButton;
}

/**
* @todo the VizPanelMenu interface is overly restrictive, doesn't allow any member functions on this class, so everything is currently inlined
*/
export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPanelMenu, SceneObject {
constructor(state: Partial<PanelMenuState>) {
super({ ...state, addExplorationsLink: state.addExplorationsLink ?? true });
this.addActivationHandler(() => {
let exploreUrl: Promise<string | undefined> | undefined;
try {
const viz = sceneGraph.getAncestor(this, VizPanel);
const panelData = sceneGraph.getData(viz).state.data;
if (!panelData) {
throw new Error('Cannot get link to explore, no panel data found');
}
const queryRunner = getQueryRunnerFor(viz);
const queries = queryRunner?.state.queries ?? [];
queries.forEach((query) => {
// removing legendFormat to get verbose legend in Explore
delete query.legendFormat;
});
const trail = getTrailFor(this);
exploreUrl = getExploreURL(panelData, trail, panelData.timeRange);
} catch (e) {}

// Navigation options (all panels)
const items: PanelMenuItem[] = [
{
text: 'Navigation',
type: 'group',
},
{
text: 'Explore',
iconClassName: 'compass',
onClick: () => exploreUrl?.then((url) => url && window.open(url, '_blank')),
shortcut: 'p x',
},
];

this.setState({
body: new VizPanelMenu({
items,
}),
});

const addToExplorationsButton = new AddToExplorationButton({
labelName: this.state.labelName,
fieldName: this.state.fieldName,
frame: this.state.frame,
});
this._subs.add(
addToExplorationsButton?.subscribeToState(() => {
subscribeToAddToExploration(this);
})
);
this.setState({
explorationsButton: addToExplorationsButton,
});

if (this.state.addExplorationsLink) {
this.state.explorationsButton?.activate();
}
});
}

addItem(item: PanelMenuItem): void {
if (this.state.body) {
this.state.body.addItem(item);
}
}

setItems(items: PanelMenuItem[]): void {
if (this.state.body) {
this.state.body.setItems(items);
}
}

public static Component = ({ model }: SceneComponentProps<PanelMenu>) => {
const { body } = model.useState();

if (body) {
return <body.Component model={body} />;
}

return <></>;
};
}

const getInvestigationLink = (addToExplorations: AddToExplorationButton) => {
const links = getPluginLinkExtensions({
extensionPointId: extensionPointId,
context: addToExplorations.state.context,
});

return links.extensions[0];
};

const onAddToInvestigationClick = (event: React.MouseEvent, addToExplorations: AddToExplorationButton) => {
const link = getInvestigationLink(addToExplorations);
if (link && link.onClick) {
link.onClick(event);
}
};

function subscribeToAddToExploration(menu: PanelMenu) {
const addToExplorationButton = menu.state.explorationsButton;
if (addToExplorationButton) {
const link = getInvestigationLink(addToExplorationButton);

const existingMenuItems = menu.state.body?.state.items ?? [];

const existingAddToExplorationLink = existingMenuItems.find((item) => item.text === ADD_TO_INVESTIGATION_MENU_TEXT);

if (link) {
if (!existingAddToExplorationLink) {
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT,
type: 'divider',
});
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_GROUP_TEXT,
type: 'group',
});
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_TEXT,
iconClassName: 'plus-square',
onClick: (e) => onAddToInvestigationClick(e, addToExplorationButton),
});
} else {
if (existingAddToExplorationLink) {
menu.state.body?.setItems(
existingMenuItems.filter(
(item) =>
[
ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT,
ADD_TO_INVESTIGATION_MENU_GROUP_TEXT,
ADD_TO_INVESTIGATION_MENU_TEXT,
].includes(item.text) === false
)
);
}
}
}
}
}
9 changes: 2 additions & 7 deletions src/MetricSelect/MetricSelectScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import { MetricScene } from '../MetricScene';
import { getMetricNames } from './api';
import { setOtelExperienceToggleState } from '../services/store';
import { getFilters, getTrailFor } from '../utils';
import { AddToExplorationButton } from './AddToExplorationsButton';
import { getPreviewPanelFor } from './previewPanel';
import { SelectMetricAction } from './SelectMetricAction';
import {
Expand Down Expand Up @@ -423,8 +422,7 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> i
}
// refactor this into the query generator in future
const isNative = trail.isNativeHistogram(metric.name);
const panel = getPreviewPanelFor(metric.name, index, currentFilterCount, description, isNative);

const panel = getPreviewPanelFor(metric.name, index, currentFilterCount, description, isNative, true);
metric.itemRef = panel.getRef();
metric.isPanel = true;
children.push(panel);
Expand Down Expand Up @@ -646,10 +644,7 @@ function getCardPanelFor(metric: string, description?: string) {
return PanelBuilders.text()
.setTitle(metric)
.setDescription(description)
.setHeaderActions([
new SelectMetricAction({ metric, title: 'Select' }),
new AddToExplorationButton({ labelName: metric }),
])
.setHeaderActions([new SelectMetricAction({ metric, title: 'Select' })])
.setOption('content', '')
.build();
}
Expand Down
21 changes: 13 additions & 8 deletions src/MetricSelect/previewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { type PromQuery } from '@grafana/prometheus';
import { SceneCSSGridItem, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';

import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
import { PanelMenu } from '../Menu/PanelMenu';
import { getVariablesWithMetricConstant, MDP_METRIC_PREVIEW, trailDS } from '../shared';
import { getColorByIndex } from '../utils';
import { AddToExplorationButton } from './AddToExplorationsButton';
import { hideEmptyPreviews } from './hideEmptyPreviews';
import { NativeHistogramBadge } from './NativeHistogramBadge';
import { SelectMetricAction } from './SelectMetricAction';
Expand All @@ -14,24 +14,29 @@ export function getPreviewPanelFor(
index: number,
currentFilterCount: number,
description?: string,
nativeHistogram?: boolean
nativeHistogram?: boolean,
hideMenu?: boolean
) {
const autoQuery = getAutoQueriesForMetric(metric, nativeHistogram);
let actions: Array<SelectMetricAction | AddToExplorationButton | NativeHistogramBadge> = [
new SelectMetricAction({ metric, title: 'Select' }),
new AddToExplorationButton({ labelName: metric }),
];
let actions: Array<SelectMetricAction | NativeHistogramBadge> = [new SelectMetricAction({ metric, title: 'Select' })];

if (nativeHistogram) {
actions.unshift(new NativeHistogramBadge({}));
}

const vizPanel = autoQuery.preview
let vizPanelBuilder = autoQuery.preview
.vizBuilder()
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(index) })
.setDescription(description)
.setHeaderActions(actions)
.build();
.setShowMenuAlways(true)
.setMenu(new PanelMenu({ labelName: metric }));

if (!hideMenu) {
vizPanelBuilder = vizPanelBuilder.setShowMenuAlways(true).setMenu(new PanelMenu({ labelName: metric }));
}

const vizPanel = vizPanelBuilder.build();

const queries = autoQuery.preview.queries.map((query) =>
convertPreviewQueriesToIgnoreUsage(query, currentFilterCount)
Expand Down
9 changes: 4 additions & 5 deletions src/autoQuery/components/AutoVizPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@grafana/scenes';
import React from 'react';

import { AddToExplorationButton } from '../../MetricSelect/AddToExplorationsButton';
import { PanelMenu } from '../../Menu/PanelMenu';
import { MDP_METRIC_OVERVIEW, trailDS } from '../../shared';
import { getMetricSceneFor } from '../../utils';
import { type AutoQueryDef } from '../types';
Expand Down Expand Up @@ -52,10 +52,9 @@ export class AutoVizPanel extends SceneObjectBase<AutoVizPanelState> {
queries: def.queries,
})
)
.setHeaderActions([
new AutoVizPanelQuerySelector({ queryDef: def, onChangeQuery: this.onChangeQuery }),
new AddToExplorationButton({ labelName: metric ?? this.state.metric }),
])
.setHeaderActions([new AutoVizPanelQuerySelector({ queryDef: def, onChangeQuery: this.onChangeQuery })])
.setShowMenuAlways(true)
.setMenu(new PanelMenu({ labelName: metric ?? this.state.metric }))
.build();
}

Expand Down

0 comments on commit 16f7e53

Please sign in to comment.