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

Metrics streaming support #312

Merged
merged 7 commits into from
Feb 19, 2025
Merged
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
45 changes: 27 additions & 18 deletions src/components/Explore/ByFrameRepeater.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { DataFrame, FieldType, GrafanaTheme2, LoadingState, PanelData } from '@grafana/data';
import { DataFrame, FieldType, GrafanaTheme2, LoadingState, PanelData, sortDataFrame } from '@grafana/data';
import {
SceneComponentProps,
SceneCSSGridLayout,
Expand All @@ -19,7 +19,13 @@ import { ErrorStateScene } from 'components/states/ErrorState/ErrorStateScene';
import { debounce } from 'lodash';
import { Search } from './Search';
import { getGroupByVariable } from 'utils/utils';
import { EMPTY_STATE_ERROR_MESSAGE, EMPTY_STATE_ERROR_REMEDY_MESSAGE, EventTimeseriesDataReceived, GRID_TEMPLATE_COLUMNS } from '../../utils/shared';
import {
EMPTY_STATE_ERROR_MESSAGE,
EMPTY_STATE_ERROR_REMEDY_MESSAGE,
EventTimeseriesDataReceived,
GRID_TEMPLATE_COLUMNS,
} from '../../utils/shared';
import { cloneDataFrame } from '../../utils/frames';

interface ByFrameRepeaterState extends SceneObjectState {
body: SceneLayout;
Expand All @@ -39,8 +45,8 @@ export class ByFrameRepeater extends SceneObjectBase<ByFrameRepeaterState> {

this._subs.add(
data.subscribeToState((data) => {
if (data.data?.state === LoadingState.Done) {
if (data.data.series.length === 0) {
if (data.data?.state === LoadingState.Done || data.data?.state === LoadingState.Streaming) {
if (data.data.series.length === 0 && data.data?.state !== LoadingState.Streaming) {
this.state.body.setState({
children: [
new SceneFlexItem({
Expand All @@ -66,13 +72,13 @@ export class ByFrameRepeater extends SceneObjectBase<ByFrameRepeaterState> {
new SceneCSSGridLayout({
children: [
new ErrorStateScene({
message: data.data.error?.message ?? 'An error occurred in the query',
message: data.data.errors?.[0]?.message ?? 'An error occurred in the query',
}),
],
}),
],
});
} else if (data.data?.state === LoadingState.Loading) {
} else {
this.state.body.setState({
children: [
new SceneCSSGridLayout({
Expand Down Expand Up @@ -131,24 +137,27 @@ export class ByFrameRepeater extends SceneObjectBase<ByFrameRepeaterState> {
}

private groupSeriesBy(data: PanelData, groupBy: string) {
const groupedData = data.series.reduce((acc, series) => {
const key = series.fields.find((f) => f.type === FieldType.number)?.labels?.[groupBy];
if (!key) {
const groupedData = data.series.reduce(
(acc, series) => {
const key = series.fields.find((f) => f.type === FieldType.number)?.labels?.[groupBy];
if (!key) {
return acc;
}
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(series);
return acc;
}
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(series);
return acc;
}, {} as Record<string, DataFrame[]>);
},
{} as Record<string, DataFrame[]>
);

const newSeries = [];
for (const key in groupedData) {
const frames = groupedData[key].sort((a, b) => a.name?.localeCompare(b.name!) || 0);
const mainFrame = frames[0];
const mainFrame = cloneDataFrame(frames[0]);
frames.slice(1, frames.length).forEach((frame) => mainFrame.fields.push(frame.fields[1]));
newSeries.push(mainFrame);
newSeries.push(sortDataFrame(mainFrame, 0));
}
return newSeries;
}
Expand Down
44 changes: 29 additions & 15 deletions src/components/Explore/layouts/allComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ export function buildAllComparisonLayout(
actionsFn: (df: DataFrame) => VizPanelState['headerActions'],
metric: MetricFunction
) {
const panels: Record<string, SceneCSSGridItem> = {};

return new ByFrameRepeater({
body: new SceneCSSGridLayout({
templateColumns: GRID_TEMPLATE_COLUMNS,
autoRows: '320px',
children: [],
}),
getLayoutChild: getLayoutChild(getFrameName, actionsFn, metric),
getLayoutChild: getLayoutChild(panels, getFrameName, actionsFn, metric),
});
}

Expand All @@ -28,33 +30,45 @@ const getFrameName = (df: DataFrame) => {
};

function getLayoutChild(
panels: Record<string, SceneCSSGridItem>,
getTitle: (df: DataFrame) => string,
actionsFn: (df: DataFrame) => VizPanelState['headerActions'],
metric: MetricFunction
) {
return (data: PanelData, frame: DataFrame) => {
const panel = getPanelConfig(metric)
.setTitle(getTitle(frame))
.setData(
new SceneDataNode({
data: {
...data,
series: [
{
...frame,
},
],
const existingGridItem = frame.name ? panels[frame.name] : undefined;

const dataNode = new SceneDataNode({
data: {
...data,
series: [
{
...frame,
},
})
);
],
},
});

if (existingGridItem) {
existingGridItem.state.body?.setState({ $data: dataNode });
return existingGridItem;
}

const panel = getPanelConfig(metric).setTitle(getTitle(frame)).setData(dataNode);

const actions = actionsFn(frame);
if (actions) {
panel.setHeaderActions(actions);
}
return new SceneCSSGridItem({

const gridItem = new SceneCSSGridItem({
body: new HighestDifferencePanel({ frame, panel: panel.build() }),
});
if (frame.name) {
panels[frame.name] = gridItem;
}

return gridItem;
};
}

Expand Down
49 changes: 32 additions & 17 deletions src/components/Explore/layouts/attributeBreakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function buildNormalLayout(
const traceExploration = getTraceExplorationScene(scene);
const metric = traceExploration.getMetricVariable().getValue() as MetricFunction;
const query = metricByWithStatus(metric, variable.getValueText());
const panels: Record<string, SceneCSSGridItem> = {};

return new LayoutSwitcher({
$behaviors: [syncYAxis()],
Expand Down Expand Up @@ -79,7 +80,7 @@ export function buildNormalLayout(
children: [],
}),
groupBy: true,
getLayoutChild: getLayoutChild(getLabelValue, variable, metric, actionsFn),
getLayoutChild: getLayoutChild(panels, getLabelValue, variable, metric, actionsFn),
}),
new ByFrameRepeater({
body: new SceneCSSGridLayout({
Expand All @@ -89,19 +90,40 @@ export function buildNormalLayout(
children: [],
}),
groupBy: true,
getLayoutChild: getLayoutChild(getLabelValue, variable, metric, actionsFn),
getLayoutChild: getLayoutChild(panels, getLabelValue, variable, metric, actionsFn),
}),
],
});
}

export function getLayoutChild(
panels: Record<string, SceneCSSGridItem>,
getTitle: (df: DataFrame, labelName: string) => string,
variable: CustomVariable,
metric: MetricFunction,
actionsFn: (df: DataFrame) => VizPanelState['headerActions']
) {
return (data: PanelData, frame: DataFrame) => {
const existingGridItem = frame.name ? panels[frame.name] : undefined;

const dataNode = new SceneDataNode({
data: {
...data,
annotations: data.annotations?.filter((a) => a.refId === frame.refId),
series: [
{
...frame,
fields: frame.fields.sort((a, b) => a.labels?.status?.localeCompare(b.labels?.status || '') || 0),
},
],
},
});

if (existingGridItem) {
existingGridItem.state.body?.setState({ $data: dataNode });
return existingGridItem;
}

const query = sceneGraph.interpolate(
variable,
generateMetricsQuery({
Expand All @@ -114,27 +136,20 @@ export function getLayoutChild(
const panel = (metric === 'duration' ? linesPanelConfig().setUnit('s') : barsPanelConfig())
.setTitle(getTitle(frame, variable.getValueText()))
.setMenu(new PanelMenu({ query, labelValue: getLabelValue(frame) }))
.setData(
new SceneDataNode({
data: {
...data,
annotations: data.annotations?.filter((a) => a.refId === frame.refId),
series: [
{
...frame,
fields: frame.fields.sort((a, b) => a.labels?.status?.localeCompare(b.labels?.status || '') || 0),
},
],
},
})
);
.setData(dataNode);

const actions = actionsFn(frame);
if (actions) {
panel.setHeaderActions(actions);
}
return new SceneCSSGridItem({

const gridItem = new SceneCSSGridItem({
body: panel.build(),
});
if (frame.name) {
panels[frame.name] = gridItem;
}

return gridItem;
};
}
45 changes: 30 additions & 15 deletions src/components/Explore/layouts/attributeComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function buildAttributeComparison(
const baselineField = attributeSeries?.fields.find((f) => f.name === 'Baseline');
const selectionField = attributeSeries?.fields.find((f) => f.name === 'Selection');

const panels: Record<string, SceneCSSGridItem> = {};

if (nameField && baselineField && selectionField) {
for (let i = 0; i < nameField.values.length; i++) {
if (!nameField.values[i] || (!baselineField.values[i] && !selectionField.values[i])) {
Expand Down Expand Up @@ -92,7 +94,7 @@ export function buildAttributeComparison(
isLazy: true,
children: [],
}),
getLayoutChild: getLayoutChild(getLabel, actionsFn, metric),
getLayoutChild: getLayoutChild(panels, getLabel, actionsFn, metric),
});
}

Expand All @@ -101,31 +103,44 @@ const getLabel = (df: DataFrame) => {
};

function getLayoutChild(
panels: Record<string, SceneCSSGridItem>,
getTitle: (df: DataFrame) => string,
actionsFn: (df: DataFrame) => VizPanelState['headerActions'],
metric: MetricFunction
) {
return (data: PanelData, frame: DataFrame) => {
const panel = getPanelConfig(metric)
.setTitle(getTitle(frame))
.setData(
new SceneDataNode({
data: {
...data,
series: [
{
...frame,
},
],
const existingGridItem = frame.name ? panels[frame.name] : undefined;

const dataNode = new SceneDataNode({
data: {
...data,
series: [
{
...frame,
},
})
);
],
},
});

if (existingGridItem) {
existingGridItem.state.body?.setState({ $data: dataNode });
return existingGridItem;
}

const panel = getPanelConfig(metric).setTitle(getTitle(frame)).setData(dataNode);

const actions = actionsFn(frame);
if (actions) {
panel.setHeaderActions(actions);
}
return new SceneCSSGridItem({

const gridItem = new SceneCSSGridItem({
body: panel.build(),
});
if (frame.name) {
panels[frame.name] = gridItem;
}

return gridItem;
};
}
Loading
Loading