mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore Metrics: Add panel menu with "Explore" and "Investigations" options (#98900)
* Explore Metrics: Add panel menu with "Explore" and "Investigations" features * Explore Metrics: Get better explore link * hide menu in metricSelect * Update public/app/features/trails/Breakdown/LabelBreakdownScene.tsx Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> * Update public/app/features/trails/MetricSelect/MetricSelectScene.tsx Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> * fix: formatting --------- Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> Co-authored-by: Nick Richmond <nick.richmond@grafana.com>
This commit is contained in:
parent
35a581a2ba
commit
154a57cd30
@ -32,8 +32,8 @@ import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { BreakdownLabelSelector } from '../BreakdownLabelSelector';
|
||||
import { DataTrail } from '../DataTrail';
|
||||
import { PanelMenu } from '../Menu/PanelMenu';
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { AddToExplorationButton } from '../MetricSelect/AddToExplorationsButton';
|
||||
import { StatusWrapper } from '../StatusWrapper';
|
||||
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
||||
import { AutoQueryDef } from '../autoQuery/types';
|
||||
@ -478,10 +478,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();
|
||||
@ -532,10 +531,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();
|
||||
|
||||
|
177
public/app/features/trails/Menu/PanelMenu.tsx
Normal file
177
public/app/features/trails/Menu/PanelMenu.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import { DataFrame, PanelMenuItem } from '@grafana/data';
|
||||
import { getPluginLinkExtensions } from '@grafana/runtime';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
sceneGraph,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
VizPanel,
|
||||
VizPanelMenu,
|
||||
} from '@grafana/scenes';
|
||||
import { getExploreUrl } from 'app/core/utils/explore';
|
||||
import { getQueryRunnerFor } from 'app/features/dashboard-scene/utils/utils';
|
||||
|
||||
import { AddToExplorationButton, extensionPointId } from '../MetricSelect/AddToExplorationsButton';
|
||||
import { getDataSource, getTrailFor } from '../utils';
|
||||
|
||||
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 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);
|
||||
const dsValue = getDataSource(trail);
|
||||
const timeRange = sceneGraph.getTimeRange(this);
|
||||
exploreUrl = getExploreUrl({
|
||||
queries,
|
||||
dsRef: { uid: dsValue },
|
||||
timeRange: timeRange.state.value,
|
||||
scopedVars: { __sceneObject: { value: viz } },
|
||||
});
|
||||
} 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -45,7 +45,6 @@ import {
|
||||
} from '../shared';
|
||||
import { getFilters, getTrailFor, isSceneTimeRangeState } from '../utils';
|
||||
|
||||
import { AddToExplorationButton } from './AddToExplorationsButton';
|
||||
import { SelectMetricAction } from './SelectMetricAction';
|
||||
import { getMetricNames } from './api';
|
||||
import { getPreviewPanelFor } from './previewPanel';
|
||||
@ -424,7 +423,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;
|
||||
@ -655,10 +654,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();
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { PromQuery } from '@grafana/prometheus';
|
||||
import { SceneCSSGridItem, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';
|
||||
|
||||
import { PanelMenu } from '../Menu/PanelMenu';
|
||||
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
||||
import { getVariablesWithMetricConstant, MDP_METRIC_PREVIEW, trailDS } from '../shared';
|
||||
import { getColorByIndex } from '../utils';
|
||||
|
||||
import { AddToExplorationButton } from './AddToExplorationsButton';
|
||||
import { NativeHistogramBadge } from './NativeHistogramBadge';
|
||||
import { SelectMetricAction } from './SelectMetricAction';
|
||||
import { hideEmptyPreviews } from './hideEmptyPreviews';
|
||||
@ -15,24 +15,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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SceneObjectState, SceneObjectBase, SceneComponentProps, VizPanel, SceneQueryRunner } from '@grafana/scenes';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState, SceneQueryRunner, VizPanel } from '@grafana/scenes';
|
||||
|
||||
import { AddToExplorationButton } from '../../MetricSelect/AddToExplorationsButton';
|
||||
import { PanelMenu } from '../../Menu/PanelMenu';
|
||||
import { getMetricDescription } from '../../helpers/MetricDatasourceHelper';
|
||||
import { MDP_METRIC_OVERVIEW, trailDS } from '../../shared';
|
||||
import { getMetricSceneFor, getTrailFor } from '../../utils';
|
||||
@ -57,10 +57,9 @@ export class AutoVizPanel extends SceneObjectBase<AutoVizPanelState> {
|
||||
})
|
||||
)
|
||||
.setDescription(description)
|
||||
.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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user