Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(exploration): add grafana-lokiexplore-app/metric-exploration/v1 entrypoint #840

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/Components/ServiceScene/Breakdowns/AddToExplorationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useMemo } from 'react';
import { DataFrame, DataSourceJsonData, TimeRange } from '@grafana/data';
import { DataSourceWithBackend, usePluginLinks } from '@grafana/runtime';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState, SceneQueryRunner } from '@grafana/scenes';
import { DataQuery, DataSourceRef } from '@grafana/schema';
import { IconButton } from '@grafana/ui';
import { getLokiDatasource } from 'services/scenes';

export interface AddToExplorationButtonState extends SceneObjectState {
frame?: DataFrame;
ds?: DataSourceWithBackend<DataQuery, DataSourceJsonData>;
labelName?: string;
fieldName?: string;
}

type ExtensionContext = {
timeRange: TimeRange;
queries: DataQuery[];
datasource: DataSourceRef;
origin: string;
url: string;
type: string;
title: string;
note?: string;
id: string;
};

export class AddToExplorationButton extends SceneObjectBase<AddToExplorationButtonState> {
constructor(state: AddToExplorationButtonState) {
super(state);
this.addActivationHandler(this.onActivate);
}

private onActivate = () => {
getLokiDatasource(this).then((ds) => {
this.setState({ ds });
});
};

public static Component = ({ model }: SceneComponentProps<AddToExplorationButton>) => {
const { ds, frame, labelName, fieldName } = model.useState();

const data = sceneGraph.getData(model);
const sqr = sceneGraph.findObject(data, (o) => o instanceof SceneQueryRunner) as SceneQueryRunner;

const queries = useMemo(() => {
return sqr?.state.queries.map((q) => ({
...q,
expr: sceneGraph.interpolate(sqr, q.expr),
legendFormat: sceneGraph.interpolate(sqr, q.legendFormat),
}));
}, [sqr]);

const datasourceUid = ds?.uid;
const timeRange = sceneGraph.getTimeRange(model);

useMemo(() => {
if (frame) {
const filter = getFilter(frame);
queries?.forEach((query: DataQuery & { legendFormat: string }) => {
if (filter) {
query.legendFormat = `{{${filter.name}}}`;
}
});
}
}, [frame, queries]);

const extensionPointId = 'grafana-lokiexplore-app/metric-exploration/v1';
const context = useMemo<ExtensionContext | undefined>(() => {
if (!timeRange || !queries || !datasourceUid) {
return;
}
return {
timeRange: { ...timeRange.state.value },
type: 'timeseries',
queries,
datasource: { uid: datasourceUid },
origin: 'Explore Logs',
url: window.location.href,
id: `${JSON.stringify(queries)}${labelName}`,
title: `${labelName}${fieldName ? ` > ${fieldName}` : ''}`,
};
}, [datasourceUid, timeRange, queries, labelName, fieldName]);

const { links } = usePluginLinks({ extensionPointId, context });

return (
<>
{links
.filter((link) => link.pluginId === 'grafana-investigations-app')
.map((link) => (
<IconButton
tooltip={link.description}
disabled={link.category === 'disabled'}
aria-label="extension-link-to-open-exploration"
key={link.id}
name={link.icon ?? 'panel-add'}
onClick={link.onClick}
/>
))}
</>
);
};
}

const getFilter = (frame: DataFrame) => {
const filterNameAndValueObj = frame.fields[1]?.labels ?? {};
if (Object.keys(filterNameAndValueObj).length !== 1) {
return;
}
const name = Object.keys(filterNameAndValueObj)[0];
return { name, value: filterNameAndValueObj[name] };
};
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ export class FieldValuesBreakdownScene extends SceneObjectBase<FieldValuesBreakd
getLabelValue,
query?.expr.includes('count_over_time') ? DrawStyle.Bars : DrawStyle.Line,
parserForThisField === 'structuredMetadata' ? VAR_METADATA : VAR_FIELDS,
sceneGraph.getAncestor(this, FieldsBreakdownScene).state.sort
sceneGraph.getAncestor(this, FieldsBreakdownScene).state.sort,
optionValue
),
sortBy,
direction,
Expand All @@ -224,7 +225,8 @@ export class FieldValuesBreakdownScene extends SceneObjectBase<FieldValuesBreakd
getLabelValue,
query?.expr.includes('count_over_time') ? DrawStyle.Bars : DrawStyle.Line,
parserForThisField === 'structuredMetadata' ? VAR_METADATA : VAR_FIELDS,
sceneGraph.getAncestor(this, FieldsBreakdownScene).state.sort
sceneGraph.getAncestor(this, FieldsBreakdownScene).state.sort,
optionValue
),
sortBy,
direction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { limitMaxNumberOfSeriesForPanel, MAX_NUMBER_OF_TIME_SERIES } from './Tim
import { map, Observable } from 'rxjs';
import { buildFieldsQueryString, isAvgField } from '../../../services/fields';
import { getFieldGroupByVariable, getFieldsVariable } from '../../../services/variableGetters';
import { AddToExplorationButton } from './AddToExplorationButton';

export interface FieldsAggregatedBreakdownSceneState extends SceneObjectState {
body?: LayoutSwitcher;
Expand Down Expand Up @@ -214,24 +215,26 @@ export class FieldsAggregatedBreakdownScene extends SceneObjectBase<FieldsAggreg
});
let body = PanelBuilders.timeseries().setTitle(optionValue).setData(dataTransformer);

const headerActions = [];
if (!isAvgField(optionValue)) {
body = body
.setHeaderActions(new SelectLabelActionScene({ labelName: String(optionValue), fieldType: ValueSlugs.field }))
.setCustomFieldConfig('stacking', { mode: StackingMode.Normal })
.setCustomFieldConfig('fillOpacity', 100)
.setCustomFieldConfig('lineWidth', 0)
.setCustomFieldConfig('pointSize', 0)
.setCustomFieldConfig('drawStyle', DrawStyle.Bars)
.setOverrides(setLevelColorOverrides);
headerActions.push(new SelectLabelActionScene({ labelName: String(optionValue), fieldType: ValueSlugs.field }));
} else {
body = body.setHeaderActions(
headerActions.push(
new SelectLabelActionScene({
labelName: String(optionValue),
hideValueDrilldown: true,
fieldType: ValueSlugs.field,
})
);
}
body.setHeaderActions([...headerActions, new AddToExplorationButton({ labelName: optionValue })]);

const viz = body.build();
const gridItem = new SceneCSSGridItem({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ export class LabelValuesBreakdownScene extends SceneObjectBase<LabelValueBreakdo
getLabelValue,
DrawStyle.Bars,
VAR_LABELS,
sceneGraph.getAncestor(this, LabelBreakdownScene).state.sort
sceneGraph.getAncestor(this, LabelBreakdownScene).state.sort,
tagKey
),
sortBy,
direction,
Expand All @@ -247,7 +248,8 @@ export class LabelValuesBreakdownScene extends SceneObjectBase<LabelValueBreakdo
getLabelValue,
DrawStyle.Bars,
VAR_LABELS,
sceneGraph.getAncestor(this, LabelBreakdownScene).state.sort
sceneGraph.getAncestor(this, LabelBreakdownScene).state.sort,
tagKey
),
sortBy,
direction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getFieldsVariable, getLabelGroupByVariable } from '../../../services/va
import { LokiQuery } from '../../../services/lokiQuery';
import { ServiceScene } from '../ServiceScene';
import { DataFrame, LoadingState } from '@grafana/data';
import { AddToExplorationButton } from './AddToExplorationButton';

export interface LabelsAggregatedBreakdownSceneState extends SceneObjectState {
body?: LayoutSwitcher;
Expand Down Expand Up @@ -223,7 +224,10 @@ export class LabelsAggregatedBreakdownScene extends SceneObjectBase<LabelsAggreg
body: PanelBuilders.timeseries()
.setTitle(optionValue)
.setData(dataTransformer)
.setHeaderActions(new SelectLabelActionScene({ labelName: optionValue, fieldType: ValueSlugs.label }))
.setHeaderActions([
new SelectLabelActionScene({ labelName: optionValue, fieldType: ValueSlugs.label }),
new AddToExplorationButton({ labelName: optionValue }),
])
.setCustomFieldConfig('stacking', { mode: StackingMode.Normal })
.setCustomFieldConfig('fillOpacity', 100)
.setCustomFieldConfig('lineWidth', 0)
Expand Down
5 changes: 5 additions & 0 deletions src/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@
"title": "Open in Explore Logs",
"description": "Open current query in the Explore Logs view"
}
],
"extensionPoints": [
{
"id": "grafana-lokiexplore-app/metric-exploration/v1"
}
]
}
}
9 changes: 7 additions & 2 deletions src/services/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { averageFields } from '../Components/ServiceScene/Breakdowns/FieldsBreak
import { getLogsStreamSelector, getValueFromFieldsFilter } from './variableGetters';
import { LabelType } from './fieldsTypes';
import { logger } from './logger';
import { AddToExplorationButton } from 'Components/ServiceScene/Breakdowns/AddToExplorationButton';

export type DetectedLabel = {
label: string;
Expand Down Expand Up @@ -118,7 +119,8 @@ export function getFilterBreakdownValueScene(
getTitle: (df: DataFrame) => string,
style: DrawStyle,
variableName: typeof VAR_FIELDS | typeof VAR_LABELS | typeof VAR_METADATA,
sortByScene: SortByScene
sortByScene: SortByScene,
labelKey?: string
) {
return (frame: DataFrame, frameIndex: number) => {
const reducerID = getReducerId(sortByScene.state.sortBy);
Expand All @@ -133,7 +135,10 @@ export function getFilterBreakdownValueScene(
)
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
.setOverrides(setLevelColorOverrides)
.setHeaderActions(new AddToFiltersButton({ frame, variableName }));
.setHeaderActions([
new AddToFiltersButton({ frame, variableName }),
new AddToExplorationButton({ frame, fieldName: getTitle(frame), labelName: labelKey }),
]);

if (style === DrawStyle.Bars) {
panel
Expand Down
Loading