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

Remove go to explore button, add PanelMenu to logs & table panels #942

Merged
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
37 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
34378be
chore: remove go to explore button, add PanelMenus to logs and table …
gtk-grafana Dec 9, 2024
e9b9d71
chore: remove explorations link for logs/table panels
gtk-grafana Dec 9, 2024
9c6bdcc
chore: clean up
gtk-grafana Dec 9, 2024
7e548d6
Merge branch 'main' into gtk-grafana/issues/862/drilldown-values-ui-u…
gtk-grafana Dec 9, 2024
0cb621a
Merge remote-tracking branch 'origin/gtk-grafana/issues/862/drilldown…
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
cdd98f3
Merge branch 'gtk-grafana/issues/862/drilldown-values-ui-updates' int…
gtk-grafana Dec 10, 2024
0490f76
Merge remote-tracking branch 'origin/main' into gtk-grafana/issues/93…
gtk-grafana Dec 10, 2024
2312ff0
test: fix broken e2e tests
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
207 changes: 133 additions & 74 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,88 +37,45 @@ export enum AvgFieldPanelType {
'histogram' = 'histogram',
}

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

interface PanelMenuState extends SceneObjectState {
body?: VizPanelMenu;
frame?: DataFrame;
labelName?: string;
fieldName?: string;
addToExplorations?: AddToExplorationButton;
addExplorationsLink?: boolean;
explorationsButton?: AddToExplorationButton;
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);
super({ ...state, addExplorationsLink: state.addExplorationsLink ?? true });
this.addActivationHandler(() => {
const viz = findObjectOfType(this, (o) => o instanceof VizPanel, VizPanel);

this.setState({
addToExplorations: new AddToExplorationButton({
explorationsButton: new AddToExplorationButton({
gtk-grafana marked this conversation as resolved.
Show resolved Hide resolved
labelName: this.state.labelName,
fieldName: this.state.fieldName,
frame: this.state.frame,
}),
});

// @todo rewrite the AddToExplorationButton
// Manually activate scene
this.state.addToExplorations?.activate();
if (this.state.addExplorationsLink) {
// @todo rewrite the AddToExplorationButton
gtk-grafana marked this conversation as resolved.
Show resolved Hide resolved
// Manually activate scene
this.state.explorationsButton?.activate();
}

// Navigation options (all panels)
const items: PanelMenuItem[] = [
{
text: 'Navigation',
Expand All @@ -128,6 +89,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 +108,11 @@ export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPan
}),
});

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

Expand All @@ -166,20 +138,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 ? CollapsablePanelType.expanded : CollapsablePanelType.collapsed,
iconClassName: viz.state.collapsed ? 'table-collapse-all' : 'table-expand-all',
onClick: () => {
const newCollapsableState = viz.state.collapsed ? CollapsablePanelType.expanded : CollapsablePanelType.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 @@ -215,7 +274,7 @@ const onAddToInvestigationClick = (event: React.MouseEvent, addToExplorations: A
};

function subscribeToAddToExploration(exploreLogsVizPanelMenu: PanelMenu) {
const addToExplorationButton = exploreLogsVizPanelMenu.state.addToExplorations;
const addToExplorationButton = exploreLogsVizPanelMenu.state.explorationsButton;
if (addToExplorationButton) {
const link = getInvestigationLink(addToExplorationButton);

Expand Down Expand Up @@ -261,7 +320,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
4 changes: 0 additions & 4 deletions src/Components/ServiceScene/ActionBarScene.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { Box, Dropdown, Menu, Stack, Tab, TabsBar, ToolbarButton, useStyles2 } from '@grafana/ui';
import { getExplorationFor } from '../../services/scenes';
import { getDrilldownSlug, getDrilldownValueSlug, PageSlugs, ValueSlugs } from '../../services/routing';
import { GoToExploreButton } from './GoToExploreButton';
import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from '../../services/analytics';
import { navigateToDrilldownPage } from '../../services/navigate';
import React, { useEffect, useState } from 'react';
Expand All @@ -18,7 +16,6 @@ export interface ActionBarSceneState extends SceneObjectState {}
export class ActionBarScene extends SceneObjectBase<ActionBarSceneState> {
public static Component = ({ model }: SceneComponentProps<ActionBarScene>) => {
const styles = useStyles2(getStyles);
const exploration = getExplorationFor(model);
let currentBreakdownViewSlug = getDrilldownSlug();
let allowNavToParent = false;

Expand All @@ -42,7 +39,6 @@ export class ActionBarScene extends SceneObjectBase<ActionBarSceneState> {
<div className={styles.actions}>
<Stack gap={1}>
{config.featureToggles.appSidecar && <ToolbarExtensionsRenderer serviceScene={serviceScene} />}
<GoToExploreButton exploration={exploration} />
</Stack>
</div>

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