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 { BreakdownLabelSelector } from '../BreakdownLabelSelector';
|
||||||
import { DataTrail } from '../DataTrail';
|
import { DataTrail } from '../DataTrail';
|
||||||
|
import { PanelMenu } from '../Menu/PanelMenu';
|
||||||
import { MetricScene } from '../MetricScene';
|
import { MetricScene } from '../MetricScene';
|
||||||
import { AddToExplorationButton } from '../MetricSelect/AddToExplorationsButton';
|
|
||||||
import { StatusWrapper } from '../StatusWrapper';
|
import { StatusWrapper } from '../StatusWrapper';
|
||||||
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
||||||
import { AutoQueryDef } from '../autoQuery/types';
|
import { AutoQueryDef } from '../autoQuery/types';
|
||||||
@ -478,10 +478,9 @@ export function buildAllLayout(
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.setHeaderActions([
|
.setHeaderActions([new SelectLabelAction({ labelName: String(option.value) })])
|
||||||
new SelectLabelAction({ labelName: String(option.value) }),
|
.setShowMenuAlways(true)
|
||||||
new AddToExplorationButton({ labelName: String(option.value) }),
|
.setMenu(new PanelMenu({ labelName: String(option.value) }))
|
||||||
])
|
|
||||||
.setUnit(unit)
|
.setUnit(unit)
|
||||||
.setBehaviors([fixLegendForUnspecifiedLabelValueBehavior])
|
.setBehaviors([fixLegendForUnspecifiedLabelValueBehavior])
|
||||||
.build();
|
.build();
|
||||||
@ -532,10 +531,9 @@ function buildNormalLayout(
|
|||||||
.setTitle(getLabelValue(frame))
|
.setTitle(getLabelValue(frame))
|
||||||
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
|
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
|
||||||
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
|
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
|
||||||
.setHeaderActions([
|
.setHeaderActions([new AddToFiltersGraphAction({ frame })])
|
||||||
new AddToFiltersGraphAction({ frame }),
|
.setShowMenuAlways(true)
|
||||||
new AddToExplorationButton({ labelName: getLabelValue(frame) }),
|
.setMenu(new PanelMenu({ labelName: getLabelValue(frame) }))
|
||||||
])
|
|
||||||
.setUnit(unit)
|
.setUnit(unit)
|
||||||
.build();
|
.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';
|
} from '../shared';
|
||||||
import { getFilters, getTrailFor, isSceneTimeRangeState } from '../utils';
|
import { getFilters, getTrailFor, isSceneTimeRangeState } from '../utils';
|
||||||
|
|
||||||
import { AddToExplorationButton } from './AddToExplorationsButton';
|
|
||||||
import { SelectMetricAction } from './SelectMetricAction';
|
import { SelectMetricAction } from './SelectMetricAction';
|
||||||
import { getMetricNames } from './api';
|
import { getMetricNames } from './api';
|
||||||
import { getPreviewPanelFor } from './previewPanel';
|
import { getPreviewPanelFor } from './previewPanel';
|
||||||
@ -424,7 +423,7 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> i
|
|||||||
}
|
}
|
||||||
// refactor this into the query generator in future
|
// refactor this into the query generator in future
|
||||||
const isNative = trail.isNativeHistogram(metric.name);
|
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.itemRef = panel.getRef();
|
||||||
metric.isPanel = true;
|
metric.isPanel = true;
|
||||||
@ -655,10 +654,7 @@ function getCardPanelFor(metric: string, description?: string) {
|
|||||||
return PanelBuilders.text()
|
return PanelBuilders.text()
|
||||||
.setTitle(metric)
|
.setTitle(metric)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setHeaderActions([
|
.setHeaderActions([new SelectMetricAction({ metric, title: 'Select' })])
|
||||||
new SelectMetricAction({ metric, title: 'Select' }),
|
|
||||||
new AddToExplorationButton({ labelName: metric }),
|
|
||||||
])
|
|
||||||
.setOption('content', '')
|
.setOption('content', '')
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { PromQuery } from '@grafana/prometheus';
|
import { PromQuery } from '@grafana/prometheus';
|
||||||
import { SceneCSSGridItem, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';
|
import { SceneCSSGridItem, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { PanelMenu } from '../Menu/PanelMenu';
|
||||||
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
import { getAutoQueriesForMetric } from '../autoQuery/getAutoQueriesForMetric';
|
||||||
import { getVariablesWithMetricConstant, MDP_METRIC_PREVIEW, trailDS } from '../shared';
|
import { getVariablesWithMetricConstant, MDP_METRIC_PREVIEW, trailDS } from '../shared';
|
||||||
import { getColorByIndex } from '../utils';
|
import { getColorByIndex } from '../utils';
|
||||||
|
|
||||||
import { AddToExplorationButton } from './AddToExplorationsButton';
|
|
||||||
import { NativeHistogramBadge } from './NativeHistogramBadge';
|
import { NativeHistogramBadge } from './NativeHistogramBadge';
|
||||||
import { SelectMetricAction } from './SelectMetricAction';
|
import { SelectMetricAction } from './SelectMetricAction';
|
||||||
import { hideEmptyPreviews } from './hideEmptyPreviews';
|
import { hideEmptyPreviews } from './hideEmptyPreviews';
|
||||||
@ -15,24 +15,29 @@ export function getPreviewPanelFor(
|
|||||||
index: number,
|
index: number,
|
||||||
currentFilterCount: number,
|
currentFilterCount: number,
|
||||||
description?: string,
|
description?: string,
|
||||||
nativeHistogram?: boolean
|
nativeHistogram?: boolean,
|
||||||
|
hideMenu?: boolean
|
||||||
) {
|
) {
|
||||||
const autoQuery = getAutoQueriesForMetric(metric, nativeHistogram);
|
const autoQuery = getAutoQueriesForMetric(metric, nativeHistogram);
|
||||||
let actions: Array<SelectMetricAction | AddToExplorationButton | NativeHistogramBadge> = [
|
let actions: Array<SelectMetricAction | NativeHistogramBadge> = [new SelectMetricAction({ metric, title: 'Select' })];
|
||||||
new SelectMetricAction({ metric, title: 'Select' }),
|
|
||||||
new AddToExplorationButton({ labelName: metric }),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (nativeHistogram) {
|
if (nativeHistogram) {
|
||||||
actions.unshift(new NativeHistogramBadge({}));
|
actions.unshift(new NativeHistogramBadge({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const vizPanel = autoQuery.preview
|
let vizPanelBuilder = autoQuery.preview
|
||||||
.vizBuilder()
|
.vizBuilder()
|
||||||
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(index) })
|
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(index) })
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setHeaderActions(actions)
|
.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) =>
|
const queries = autoQuery.preview.queries.map((query) =>
|
||||||
convertPreviewQueriesToIgnoreUsage(query, currentFilterCount)
|
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 { getMetricDescription } from '../../helpers/MetricDatasourceHelper';
|
||||||
import { MDP_METRIC_OVERVIEW, trailDS } from '../../shared';
|
import { MDP_METRIC_OVERVIEW, trailDS } from '../../shared';
|
||||||
import { getMetricSceneFor, getTrailFor } from '../../utils';
|
import { getMetricSceneFor, getTrailFor } from '../../utils';
|
||||||
@ -57,10 +57,9 @@ export class AutoVizPanel extends SceneObjectBase<AutoVizPanelState> {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setHeaderActions([
|
.setHeaderActions([new AutoVizPanelQuerySelector({ queryDef: def, onChangeQuery: this.onChangeQuery })])
|
||||||
new AutoVizPanelQuerySelector({ queryDef: def, onChangeQuery: this.onChangeQuery }),
|
.setShowMenuAlways(true)
|
||||||
new AddToExplorationButton({ labelName: metric ?? this.state.metric }),
|
.setMenu(new PanelMenu({ labelName: metric ?? this.state.metric }))
|
||||||
])
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user