Chore: Refactor explore metrics layout switcher and breakdown scene (#90944)
* refactor breakdown scene * refactor BreakdownScene along with LayoutSwitcher * rename * don't pass default layout * better type handling * betterer
This commit is contained in:
+2
-3
@@ -5441,9 +5441,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
|
||||
],
|
||||
"public/app/features/trails/MetricScene.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
|
||||
],
|
||||
"public/app/features/trails/MetricSelect/MetricSelectScene.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
|
||||
@@ -37,6 +37,7 @@ import { AddToFiltersGraphAction } from './AddToFiltersGraphAction';
|
||||
import { ByFrameRepeater } from './ByFrameRepeater';
|
||||
import { LayoutSwitcher } from './LayoutSwitcher';
|
||||
import { breakdownPanelOptions } from './panelConfigs';
|
||||
import { BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types';
|
||||
import { getLabelOptions } from './utils';
|
||||
import { BreakdownAxisChangeEvent, yAxisSyncBehavior } from './yAxisSyncBehavior';
|
||||
|
||||
@@ -97,13 +98,6 @@ export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
this.clearBreakdownPanelAxisValues();
|
||||
});
|
||||
|
||||
metricScene.subscribeToState(({ layout }, old) => {
|
||||
if (layout !== old.layout) {
|
||||
// Change in layout will set up a different set of panel objects that haven't received the current yaxis range
|
||||
this.clearBreakdownPanelAxisValues();
|
||||
}
|
||||
});
|
||||
|
||||
this.updateBody(variable);
|
||||
}
|
||||
|
||||
@@ -186,8 +180,8 @@ export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
|
||||
if (!variable.state.loading && variable.state.options.length) {
|
||||
stateUpdate.body = variable.hasAllValue()
|
||||
? buildAllLayout(options, this._query!)
|
||||
: buildNormalLayout(this._query!);
|
||||
? buildAllLayout(options, this._query!, this.onBreakdownLayoutChange)
|
||||
: buildNormalLayout(this._query!, this.onBreakdownLayoutChange);
|
||||
} else if (!variable.state.loading) {
|
||||
stateUpdate.body = undefined;
|
||||
stateUpdate.blockingMessage = 'Unable to retrieve label options for currently selected metric.';
|
||||
@@ -198,6 +192,10 @@ export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
this.setState(stateUpdate);
|
||||
}
|
||||
|
||||
public onBreakdownLayoutChange = (_: BreakdownLayoutType) => {
|
||||
this.clearBreakdownPanelAxisValues();
|
||||
};
|
||||
|
||||
public onChange = (value?: string) => {
|
||||
if (!value) {
|
||||
return;
|
||||
@@ -271,7 +269,11 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAllLayout(options: Array<SelectableValue<string>>, queryDef: AutoQueryDef) {
|
||||
export function buildAllLayout(
|
||||
options: Array<SelectableValue<string>>,
|
||||
queryDef: AutoQueryDef,
|
||||
onBreakdownLayoutChange: BreakdownLayoutChangeCallback
|
||||
) {
|
||||
const children: SceneFlexItemLike[] = [];
|
||||
|
||||
for (const option of options) {
|
||||
@@ -318,11 +320,12 @@ export function buildAllLayout(options: Array<SelectableValue<string>>, queryDef
|
||||
);
|
||||
}
|
||||
return new LayoutSwitcher({
|
||||
options: [
|
||||
breakdownLayoutOptions: [
|
||||
{ value: 'grid', label: 'Grid' },
|
||||
{ value: 'rows', label: 'Rows' },
|
||||
],
|
||||
layouts: [
|
||||
onBreakdownLayoutChange,
|
||||
breakdownLayouts: [
|
||||
new SceneCSSGridLayout({
|
||||
templateColumns: GRID_TEMPLATE_COLUMNS,
|
||||
autoRows: '200px',
|
||||
@@ -342,7 +345,7 @@ export function buildAllLayout(options: Array<SelectableValue<string>>, queryDef
|
||||
|
||||
const GRID_TEMPLATE_COLUMNS = 'repeat(auto-fit, minmax(400px, 1fr))';
|
||||
|
||||
function buildNormalLayout(queryDef: AutoQueryDef) {
|
||||
function buildNormalLayout(queryDef: AutoQueryDef, onBreakdownLayoutChange: BreakdownLayoutChangeCallback) {
|
||||
const unit = queryDef.unit;
|
||||
|
||||
function getLayoutChild(data: PanelData, frame: DataFrame, frameIndex: number): SceneFlexItem {
|
||||
@@ -377,12 +380,13 @@ function buildNormalLayout(queryDef: AutoQueryDef) {
|
||||
maxDataPoints: 300,
|
||||
queries: queryDef.queries,
|
||||
}),
|
||||
options: [
|
||||
breakdownLayoutOptions: [
|
||||
{ value: 'single', label: 'Single' },
|
||||
{ value: 'grid', label: 'Grid' },
|
||||
{ value: 'rows', label: 'Rows' },
|
||||
],
|
||||
layouts: [
|
||||
onBreakdownLayoutChange,
|
||||
breakdownLayouts: [
|
||||
new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [
|
||||
|
||||
@@ -1,58 +1,85 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneObjectUrlSyncConfig,
|
||||
SceneObjectUrlValues,
|
||||
SceneObjectWithUrlSync,
|
||||
} from '@grafana/scenes';
|
||||
import { Field, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
import { TRAIL_BREAKDOWN_VIEW_KEY } from '../shared';
|
||||
import { MakeOptional, TRAIL_BREAKDOWN_VIEW_KEY } from '../shared';
|
||||
|
||||
import { LayoutType } from './types';
|
||||
import { isBreakdownLayoutType, BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types';
|
||||
|
||||
export interface LayoutSwitcherState extends SceneObjectState {
|
||||
layouts: SceneObject[];
|
||||
options: Array<SelectableValue<LayoutType>>;
|
||||
activeBreakdownLayout: BreakdownLayoutType;
|
||||
breakdownLayouts: SceneObject[];
|
||||
breakdownLayoutOptions: Array<SelectableValue<BreakdownLayoutType>>;
|
||||
onBreakdownLayoutChange: BreakdownLayoutChangeCallback;
|
||||
}
|
||||
|
||||
export class LayoutSwitcher extends SceneObjectBase<LayoutSwitcherState> {
|
||||
private getMetricScene() {
|
||||
return sceneGraph.getAncestor(this, MetricScene);
|
||||
export class LayoutSwitcher extends SceneObjectBase<LayoutSwitcherState> implements SceneObjectWithUrlSync {
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['breakdownLayout'] });
|
||||
|
||||
public constructor(state: MakeOptional<LayoutSwitcherState, 'activeBreakdownLayout'>) {
|
||||
const storedBreakdownLayout = localStorage.getItem(TRAIL_BREAKDOWN_VIEW_KEY);
|
||||
super({
|
||||
activeBreakdownLayout: isBreakdownLayoutType(storedBreakdownLayout) ? storedBreakdownLayout : 'grid',
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
getUrlState() {
|
||||
return { breakdownLayout: this.state.activeBreakdownLayout };
|
||||
}
|
||||
|
||||
updateFromUrl(values: SceneObjectUrlValues) {
|
||||
const newBreakdownLayout = values.breakdownLayout;
|
||||
if (newBreakdownLayout === 'string' && isBreakdownLayoutType(newBreakdownLayout)) {
|
||||
if (this.state.activeBreakdownLayout !== newBreakdownLayout) {
|
||||
this.setState({ activeBreakdownLayout: newBreakdownLayout });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Selector({ model }: { model: LayoutSwitcher }) {
|
||||
const { options } = model.useState();
|
||||
const activeLayout = model.useActiveLayout();
|
||||
const { activeBreakdownLayout, breakdownLayoutOptions } = model.useState();
|
||||
|
||||
return (
|
||||
<Field label="View">
|
||||
<RadioButtonGroup options={options} value={activeLayout} onChange={model.onLayoutChange} />
|
||||
<RadioButtonGroup
|
||||
options={breakdownLayoutOptions}
|
||||
value={activeBreakdownLayout}
|
||||
onChange={model.onLayoutChange}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
private useActiveLayout() {
|
||||
const { options } = this.useState();
|
||||
const { layout } = this.getMetricScene().useState();
|
||||
public onLayoutChange = (active: BreakdownLayoutType) => {
|
||||
if (this.state.activeBreakdownLayout === active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeLayout = options.map((option) => option.value).includes(layout) ? layout : options[0].value;
|
||||
return activeLayout;
|
||||
}
|
||||
|
||||
public onLayoutChange = (layout: LayoutType) => {
|
||||
reportExploreMetrics('breakdown_layout_changed', { layout });
|
||||
localStorage.setItem(TRAIL_BREAKDOWN_VIEW_KEY, layout);
|
||||
this.getMetricScene().setState({ layout });
|
||||
reportExploreMetrics('breakdown_layout_changed', { layout: active });
|
||||
localStorage.setItem(TRAIL_BREAKDOWN_VIEW_KEY, active);
|
||||
this.setState({ activeBreakdownLayout: active });
|
||||
this.state.onBreakdownLayoutChange(active);
|
||||
};
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<LayoutSwitcher>) => {
|
||||
const { layouts, options } = model.useState();
|
||||
const activeLayout = model.useActiveLayout();
|
||||
const { breakdownLayouts, breakdownLayoutOptions, activeBreakdownLayout } = model.useState();
|
||||
|
||||
const index = options.findIndex((o) => o.value === activeLayout);
|
||||
const index = breakdownLayoutOptions.findIndex((o) => o.value === activeBreakdownLayout);
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layout = layouts[index];
|
||||
const layout = breakdownLayouts[index];
|
||||
|
||||
return <layout.Component model={layout} />;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
const LAYOUT_TYPES = ['single', 'grid', 'rows'] as const;
|
||||
const BREAKDOWN_LAYOUT_TYPES = ['single', 'grid', 'rows'] as const;
|
||||
|
||||
export type LayoutType = (typeof LAYOUT_TYPES)[number];
|
||||
export type BreakdownLayoutType = (typeof BREAKDOWN_LAYOUT_TYPES)[number];
|
||||
|
||||
export function isLayoutType(layoutType: string | null | undefined): layoutType is LayoutType {
|
||||
return !!layoutType && layoutType in LAYOUT_TYPES;
|
||||
export function isBreakdownLayoutType(
|
||||
breakdownLayoutType: string | null | undefined
|
||||
): breakdownLayoutType is BreakdownLayoutType {
|
||||
return !!breakdownLayoutType && breakdownLayoutType in BREAKDOWN_LAYOUT_TYPES;
|
||||
}
|
||||
|
||||
export type BreakdownLayoutChangeCallback = (newBreakdownLayout: BreakdownLayoutType) => void;
|
||||
|
||||
@@ -3,23 +3,22 @@ import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
SceneObjectState,
|
||||
SceneObjectBase,
|
||||
QueryVariable,
|
||||
SceneComponentProps,
|
||||
sceneGraph,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneObjectUrlSyncConfig,
|
||||
SceneObjectUrlValues,
|
||||
sceneGraph,
|
||||
SceneVariableSet,
|
||||
QueryVariable,
|
||||
} from '@grafana/scenes';
|
||||
import { ToolbarButton, Box, Stack, Icon, TabsBar, Tab, useStyles2, LinkButton, Tooltip } from '@grafana/ui';
|
||||
import { Box, Icon, LinkButton, Stack, Tab, TabsBar, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getExploreUrl } from '../../core/utils/explore';
|
||||
|
||||
import { buildBreakdownActionScene } from './ActionTabs/BreakdownScene';
|
||||
import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene';
|
||||
import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene';
|
||||
import { isLayoutType, LayoutType } from './ActionTabs/types';
|
||||
import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
||||
import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types';
|
||||
import { MAIN_PANEL_MAX_HEIGHT, MAIN_PANEL_MIN_HEIGHT, MetricGraphScene } from './MetricGraphScene';
|
||||
@@ -32,7 +31,6 @@ import {
|
||||
getVariablesWithMetricConstant,
|
||||
MakeOptional,
|
||||
MetricSelectedEvent,
|
||||
TRAIL_BREAKDOWN_VIEW_KEY,
|
||||
trailDS,
|
||||
VAR_GROUP_BY,
|
||||
VAR_METRIC_EXPR,
|
||||
@@ -43,24 +41,21 @@ export interface MetricSceneState extends SceneObjectState {
|
||||
body: MetricGraphScene;
|
||||
metric: string;
|
||||
actionView?: string;
|
||||
layout: LayoutType;
|
||||
|
||||
autoQuery: AutoQueryInfo;
|
||||
queryDef?: AutoQueryDef;
|
||||
}
|
||||
|
||||
export class MetricScene extends SceneObjectBase<MetricSceneState> {
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['actionView', 'layout'] });
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['actionView'] });
|
||||
|
||||
public constructor(state: MakeOptional<MetricSceneState, 'body' | 'autoQuery' | 'layout'>) {
|
||||
public constructor(state: MakeOptional<MetricSceneState, 'body' | 'autoQuery'>) {
|
||||
const autoQuery = state.autoQuery ?? getAutoQueriesForMetric(state.metric);
|
||||
const layout = localStorage.getItem(TRAIL_BREAKDOWN_VIEW_KEY);
|
||||
super({
|
||||
$variables: state.$variables ?? getVariableSet(state.metric),
|
||||
body: state.body ?? new MetricGraphScene({}),
|
||||
autoQuery,
|
||||
queryDef: state.queryDef ?? autoQuery.main,
|
||||
layout: isLayoutType(layout) ? layout : 'grid',
|
||||
...state,
|
||||
});
|
||||
|
||||
@@ -74,7 +69,7 @@ export class MetricScene extends SceneObjectBase<MetricSceneState> {
|
||||
}
|
||||
|
||||
getUrlState() {
|
||||
return { actionView: this.state.actionView, layout: this.state.layout };
|
||||
return { actionView: this.state.actionView };
|
||||
}
|
||||
|
||||
updateFromUrl(values: SceneObjectUrlValues) {
|
||||
@@ -88,13 +83,6 @@ export class MetricScene extends SceneObjectBase<MetricSceneState> {
|
||||
} else if (values.actionView === null) {
|
||||
this.setActionView(undefined);
|
||||
}
|
||||
|
||||
if (typeof values.layout === 'string') {
|
||||
const newLayout = values.layout as LayoutType;
|
||||
if (this.state.layout !== newLayout) {
|
||||
this.setState({ layout: newLayout });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setActionView(actionView?: ActionViewType) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AdHocVariableFilter } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { LayoutType } from './ActionTabs/types';
|
||||
import { BreakdownLayoutType } from './ActionTabs/types';
|
||||
import { TrailStepType } from './DataTrailsHistory';
|
||||
import { ActionViewType } from './shared';
|
||||
|
||||
@@ -26,7 +26,7 @@ type Interactions = {
|
||||
cause: 'breakdown' | 'adhoc_filter';
|
||||
};
|
||||
// User changed the breakdown layout
|
||||
breakdown_layout_changed: { layout: LayoutType };
|
||||
breakdown_layout_changed: { layout: BreakdownLayoutType };
|
||||
// A metric exploration has started due to one of the following causes
|
||||
exploration_started: {
|
||||
cause: (
|
||||
|
||||
Reference in New Issue
Block a user