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

Value breakdowns: Update UI #936

Merged
merged 30 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d76456d
feat: rearrange menus and add new single panel above breakdowns
gtk-grafana Dec 3, 2024
9b48adf
chore: fix search
gtk-grafana Dec 3, 2024
6518e13
feat: add summary timeseries to label values breakdown
gtk-grafana Dec 3, 2024
58d4d8d
chore: refactor variable methods into new file
gtk-grafana Dec 3, 2024
0203e58
test: fix e2e assertions
gtk-grafana Dec 3, 2024
9fd65e2
test: fix flakey test
gtk-grafana Dec 3, 2024
e24225e
chore: remove only
gtk-grafana Dec 3, 2024
530cee0
chore: sync text search state
gtk-grafana Dec 3, 2024
8285424
feat: make panel collapsible
gtk-grafana Dec 4, 2024
4cbb0e5
chore: wip - refactoring series limit
gtk-grafana Dec 4, 2024
c086f61
chore: add limit to summary panel
gtk-grafana Dec 4, 2024
cdbb9f2
chore: unused import
gtk-grafana Dec 4, 2024
58bd00b
Merge remote-tracking branch 'origin/main' into gtk-grafana/issues/86…
gtk-grafana Dec 5, 2024
d32818e
chore: clean up, add series limit to summary panel
gtk-grafana Dec 5, 2024
2300cea
chore: update collapsed
gtk-grafana Dec 5, 2024
b87efa6
chore: refactor collapsable states in panel menus
gtk-grafana Dec 5, 2024
7f64aa3
chore: add findObjectOfType scenes helper method, remove type assertions
gtk-grafana Dec 5, 2024
01677e9
chore: remove unused import
gtk-grafana Dec 5, 2024
29065c0
chore: remove css hack
gtk-grafana Dec 5, 2024
67fcbaa
chore: make spacing consistent
gtk-grafana Dec 5, 2024
114b699
chore: remove search from aggregation scene, fix summary panel filtering
gtk-grafana Dec 5, 2024
82a4ffb
chore: revert 11.4 updates
gtk-grafana Dec 6, 2024
8534542
chore: remove 11.4 deps, update comments
gtk-grafana Dec 6, 2024
982d83e
chore: add e2e coverage
gtk-grafana Dec 6, 2024
7c59a8b
Merge branch 'main' into gtk-grafana/issues/862/drilldown-values-ui-u…
gtk-grafana Dec 9, 2024
afa5ae8
chore: revert yarn.lock
gtk-grafana Dec 9, 2024
7e548d6
Merge branch 'main' into gtk-grafana/issues/862/drilldown-values-ui-u…
gtk-grafana Dec 9, 2024
3159461
chore: rename variable
gtk-grafana Dec 10, 2024
1fa65d8
chore: rename no labels scene
gtk-grafana Dec 10, 2024
8feec08
chore: refactor menu names
gtk-grafana Dec 10, 2024
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
190 changes: 123 additions & 67 deletions src/Components/Panels/PanelMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
PanelBuilders,
SceneComponentProps,
SceneCSSGridItem,
SceneFlexLayout,
sceneGraph,
SceneObject,
SceneObjectBase,
Expand All @@ -12,10 +13,9 @@ import {
VizPanelMenu,
} from '@grafana/scenes';
import React from 'react';
import { css } from '@emotion/css';
import { onExploreLinkClick } from '../ServiceScene/GoToExploreButton';
import { IndexScene } from '../IndexScene/IndexScene';
import { getQueryRunnerFromChildren } from '../../services/scenes';
import { findObjectOfType, getQueryRunnerFromChildren } from '../../services/scenes';
import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from '../../services/analytics';
import { logger } from '../../services/logger';
import { AddToExplorationButton } from '../ServiceScene/Breakdowns/AddToExplorationButton';
Expand All @@ -24,6 +24,10 @@ import { ExtensionPoints } from '../../services/extensions/links';
import { setLevelColorOverrides } from '../../services/panel';
import { setPanelOption } from '../../services/store';
import { FieldsAggregatedBreakdownScene } from '../ServiceScene/Breakdowns/FieldsAggregatedBreakdownScene';
import { setValueSummaryHeight } from '../ServiceScene/Breakdowns/Panels/ValueSummary';
import { FieldValuesBreakdownScene } from '../ServiceScene/Breakdowns/FieldValuesBreakdownScene';
import { LabelValuesBreakdownScene } from '../ServiceScene/Breakdowns/LabelValuesBreakdownScene';
import { css } from '@emotion/css';

const ADD_TO_INVESTIGATION_MENU_TEXT = 'Add to investigation';
const ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT = 'Investigations';
Expand All @@ -33,6 +37,11 @@ export enum AvgFieldPanelType {
'histogram' = 'histogram',
}

export enum CollapsablePanelText {
collapsed = 'Collapse',
expanded = 'Expand',
}

interface PanelMenuState extends SceneObjectState {
body?: VizPanelMenu;
frame?: DataFrame;
Expand All @@ -42,67 +51,15 @@ interface PanelMenuState extends SceneObjectState {
panelType?: AvgFieldPanelType;
}

function addHistogramItem(items: PanelMenuItem[], sceneRef: PanelMenu) {
items.push({
text: '',
type: 'divider',
});
items.push({
text: 'Visualization',
type: 'group',
});
items.push({
text: sceneRef.state.panelType !== AvgFieldPanelType.histogram ? 'Histogram' : 'Time series',
iconClassName: sceneRef.state.panelType !== AvgFieldPanelType.histogram ? 'graph-bar' : 'chart-line',

onClick: () => {
const gridItem = sceneGraph.getAncestor(sceneRef, SceneCSSGridItem);
const viz = sceneGraph.getAncestor(sceneRef, VizPanel).clone();
const $data = sceneGraph.getData(sceneRef).clone();
const menu = sceneRef.clone();
const headerActions = Array.isArray(viz.state.headerActions)
? viz.state.headerActions.map((o) => o.clone())
: viz.state.headerActions;
let body;

if (sceneRef.state.panelType !== AvgFieldPanelType.histogram) {
body = PanelBuilders.timeseries().setOverrides(setLevelColorOverrides);
} else {
body = PanelBuilders.histogram();
}

gridItem.setState({
body: body.setMenu(menu).setTitle(viz.state.title).setHeaderActions(headerActions).setData($data).build(),
});

// @todo extend findObject and use templates to avoid type assertions
const newPanelType =
sceneRef.state.panelType !== AvgFieldPanelType.timeseries
? AvgFieldPanelType.timeseries
: AvgFieldPanelType.histogram;
setPanelOption('panelType', newPanelType);
menu.setState({ panelType: newPanelType });

const fieldsAggregatedBreakdownScene = sceneGraph.findObject(
gridItem,
(o) => o instanceof FieldsAggregatedBreakdownScene
) as FieldsAggregatedBreakdownScene | null;
if (fieldsAggregatedBreakdownScene) {
fieldsAggregatedBreakdownScene.rebuildAvgFields();
}

onSwitchVizTypeTracking(newPanelType);
},
});
}

/**
* @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);
this.addActivationHandler(() => {
const viz = sceneGraph.getAncestor(this, VizPanel);

this.setState({
addToExplorations: new AddToExplorationButton({
labelName: this.state.labelName,
Expand All @@ -115,6 +72,7 @@ export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPan
// Manually activate scene
this.state.addToExplorations?.activate();

// Navigation options (all panels)
const items: PanelMenuItem[] = [
{
text: 'Navigation',
Expand All @@ -128,6 +86,15 @@ export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPan
},
];

// Visualization options
if (this.state.panelType || viz.state.collapsible) {
addVisualizationHeader(items, this);
}

if (viz.state.collapsible) {
addCollapsableItem(items, this);
}

if (this.state.panelType) {
addHistogramItem(items, this);
}
Expand All @@ -138,9 +105,11 @@ export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPan
}),
});

this.state.addToExplorations?.subscribeToState(() => {
subscribeToAddToExploration(this);
});
this._subs.add(
this.state.addToExplorations?.subscribeToState(() => {
subscribeToAddToExploration(this);
})
);
});
}

Expand All @@ -166,20 +135,107 @@ export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPan
};
}

function addVisualizationHeader(items: PanelMenuItem[], sceneRef: PanelMenu) {
items.push({
text: '',
type: 'divider',
});
items.push({
text: 'Visualization',
type: 'group',
});
}

function addCollapsableItem(items: PanelMenuItem[], menu: PanelMenu) {
const viz = sceneGraph.getAncestor(menu, VizPanel);
items.push({
text: viz.state.collapsed ? CollapsablePanelText.expanded : CollapsablePanelText.collapsed,
iconClassName: viz.state.collapsed ? 'table-collapse-all' : 'table-expand-all',
onClick: () => {
const newCollapsableState = viz.state.collapsed ? CollapsablePanelText.expanded : CollapsablePanelText.collapsed;

// Update the viz
const vizPanelFlexLayout = sceneGraph.getAncestor(menu, SceneFlexLayout);
setValueSummaryHeight(vizPanelFlexLayout, newCollapsableState);

// Set state and update local storage
viz.setState({
collapsed: !viz.state.collapsed,
});
setPanelOption('collapsed', newCollapsableState);
},
});
}

function addHistogramItem(items: PanelMenuItem[], sceneRef: PanelMenu) {
items.push({
text: sceneRef.state.panelType !== AvgFieldPanelType.histogram ? 'Histogram' : 'Time series',
iconClassName: sceneRef.state.panelType !== AvgFieldPanelType.histogram ? 'graph-bar' : 'chart-line',

onClick: () => {
const gridItem = sceneGraph.getAncestor(sceneRef, SceneCSSGridItem);
const viz = sceneGraph.getAncestor(sceneRef, VizPanel).clone();
const $data = sceneGraph.getData(sceneRef).clone();
const menu = sceneRef.clone();
const headerActions = Array.isArray(viz.state.headerActions)
? viz.state.headerActions.map((o) => o.clone())
: viz.state.headerActions;
let body;

if (sceneRef.state.panelType !== AvgFieldPanelType.histogram) {
body = PanelBuilders.timeseries().setOverrides(setLevelColorOverrides);
} else {
body = PanelBuilders.histogram();
}

gridItem.setState({
body: body.setMenu(menu).setTitle(viz.state.title).setHeaderActions(headerActions).setData($data).build(),
});

const newPanelType =
sceneRef.state.panelType !== AvgFieldPanelType.timeseries
? AvgFieldPanelType.timeseries
: AvgFieldPanelType.histogram;
setPanelOption('panelType', newPanelType);
menu.setState({ panelType: newPanelType });

const fieldsAggregatedBreakdownScene = findObjectOfType(
gridItem,
(o) => o instanceof FieldsAggregatedBreakdownScene,
FieldsAggregatedBreakdownScene
);
if (fieldsAggregatedBreakdownScene) {
fieldsAggregatedBreakdownScene.rebuildAvgFields();
}

onSwitchVizTypeTracking(newPanelType);
},
});
}

const getExploreLink = (sceneRef: SceneObject) => {
const indexScene = sceneGraph.getAncestor(sceneRef, IndexScene);
const $data = sceneGraph.getData(sceneRef);
let queryRunner = getQueryRunnerFromChildren($data)[0];
let queryRunner = $data instanceof SceneQueryRunner ? $data : getQueryRunnerFromChildren($data)[0];

// If we don't have a query runner, then our panel is within a SceneCSSGridItem, we need to get the query runner from there
if (!queryRunner) {
const sceneGridItem = sceneGraph.getAncestor(sceneRef, SceneCSSGridItem);
const queryProvider = sceneGraph.getData(sceneGridItem);

if (queryProvider instanceof SceneQueryRunner) {
queryRunner = queryProvider;
const breakdownScene = sceneGraph.findObject(
sceneRef,
(o) => o instanceof FieldValuesBreakdownScene || o instanceof LabelValuesBreakdownScene
);
if (breakdownScene) {
const queryProvider = sceneGraph.getData(breakdownScene);

if (queryProvider instanceof SceneQueryRunner) {
queryRunner = queryProvider;
} else {
queryRunner = getQueryRunnerFromChildren(queryProvider)[0];
}
} else {
logger.error(new Error('query provider not found!'));
logger.error(new Error('Unable to locate query runner!'), {
msg: 'PanelMenu - getExploreLink: Unable to locate query runner!',
});
}
}
const uninterpolatedExpr: string | undefined = queryRunner.state.queries[0].expr;
Expand Down Expand Up @@ -261,7 +317,7 @@ export const getPanelWrapperStyles = (theme: GrafanaTheme2) => {
position: 'absolute',
display: 'flex',

// @todo remove this wrapper and styles when core changes are introduced in ???
// @todo remove this wrapper and styles when core changes are introduced in 11.5
// Need more specificity to override core style
'button.show-on-hover': {
opacity: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DataQuery, DataSourceRef } from '@grafana/schema';
import { IconButton } from '@grafana/ui';
import React from 'react';
import { ExtensionPoints } from 'services/extensions/links';
import { getLokiDatasource } from 'services/scenes';
import { findObjectOfType, getLokiDatasource } from 'services/scenes';

import LokiLogo from '../../../img/logo.svg';

Expand Down Expand Up @@ -59,7 +59,7 @@ export class AddToExplorationButton extends SceneObjectBase<AddToExplorationButt

private getQueries = () => {
const data = sceneGraph.getData(this);
const queryRunner = sceneGraph.findObject(data, (o) => o instanceof SceneQueryRunner) as SceneQueryRunner;
const queryRunner = findObjectOfType(data, (o) => o instanceof SceneQueryRunner, SceneQueryRunner);
if (queryRunner) {
const filter = this.state.frame ? getFilter(this.state.frame) : null;
const queries = queryRunner.state.queries.map((q) => ({
Expand Down
34 changes: 17 additions & 17 deletions src/Components/ServiceScene/Breakdowns/BreakdownSearchScene.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import React, { ChangeEvent } from 'react';
import { ByFrameRepeater } from './ByFrameRepeater';
import { SearchInput } from './SearchInput';
import { LabelBreakdownScene } from './LabelBreakdownScene';
import { FieldsBreakdownScene } from './FieldsBreakdownScene';
import { BusEventBase } from '@grafana/data';
import { LabelValuesBreakdownScene } from './LabelValuesBreakdownScene';
import { FieldValuesBreakdownScene } from './FieldValuesBreakdownScene';
import { logger } from '../../../services/logger';

export class BreakdownSearchReset extends BusEventBase {
Expand Down Expand Up @@ -56,21 +54,23 @@ export class BreakdownSearchScene extends SceneObjectBase<BreakdownSearchSceneSt
};

private filterValues(filter: string) {
if (this.parent instanceof LabelBreakdownScene || this.parent instanceof FieldsBreakdownScene) {
const breakdownScene = sceneGraph.findObject(
this,
(o) => o instanceof LabelBreakdownScene || o instanceof FieldsBreakdownScene
);
if (breakdownScene instanceof LabelBreakdownScene || breakdownScene instanceof FieldsBreakdownScene) {
recentFilters[this.cacheKey] = filter;
const body = this.parent.state.body;
if (body instanceof LabelValuesBreakdownScene || body instanceof FieldValuesBreakdownScene) {
body.state.body?.forEachChild((child) => {
if (child instanceof ByFrameRepeater && child.state.body.isActive) {
child.filterByString(filter);
}
});
} else {
logger.warn('invalid parent for search', {
typeofBody: typeof body,
filter,
});
}
const byFrameRepeater = sceneGraph.findDescendents(breakdownScene, ByFrameRepeater);
byFrameRepeater?.forEach((child) => {
if (child.state.body.isActive) {
child.filterByString(filter);
}
});
} else {
logger.warn('unable to find Breakdown scene', {
typeofBody: typeof breakdownScene,
filter,
});
}
}
}
Loading
Loading