mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 10:24:54 -06:00
Dashboards: Add support for systemPanelFilterVar and systemDynamicRowSizeVar variables in scenes (#93670)
Co-authored-by: kay delaney <kay@grafana.com>
This commit is contained in:
parent
bb41ff267b
commit
0c22aac7f0
@ -2774,6 +2774,9 @@ exports[`better eslint`] = {
|
||||
"public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/PanelSearchLayout.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
|
@ -71,6 +71,8 @@ import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutMana
|
||||
import { DashboardLayoutManager } from './types';
|
||||
|
||||
export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload'];
|
||||
export const PANEL_SEARCH_VAR = 'systemPanelFilterVar';
|
||||
export const PANELS_PER_ROW_VAR = 'systemDynamicRowSizeVar';
|
||||
|
||||
export interface DashboardSceneState extends SceneObjectState {
|
||||
/** The title */
|
||||
@ -119,6 +121,10 @@ export interface DashboardSceneState extends SceneObjectState {
|
||||
kioskMode?: KioskMode;
|
||||
/** Share view */
|
||||
shareView?: string;
|
||||
/** Renders panels in grid and filtered */
|
||||
panelSearch?: string;
|
||||
/** How many panels to show per row for search results */
|
||||
panelsPerRow?: number;
|
||||
}
|
||||
|
||||
export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
@ -188,6 +194,8 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
|
||||
window.__grafanaSceneContext = this;
|
||||
|
||||
this._initializePanelSearch();
|
||||
|
||||
if (this.state.isEditing) {
|
||||
this._initialUrlState = locationService.getLocation();
|
||||
this._changeTracker.startTrackingChanges();
|
||||
@ -221,6 +229,19 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
};
|
||||
}
|
||||
|
||||
private _initializePanelSearch() {
|
||||
const systemPanelFilter = sceneGraph.lookupVariable(PANEL_SEARCH_VAR, this)?.getValue();
|
||||
if (typeof systemPanelFilter === 'string') {
|
||||
this.setState({ panelSearch: systemPanelFilter });
|
||||
}
|
||||
|
||||
const panelsPerRow = sceneGraph.lookupVariable(PANELS_PER_ROW_VAR, this)?.getValue();
|
||||
if (typeof panelsPerRow === 'string') {
|
||||
const perRow = Number.parseInt(panelsPerRow, 10);
|
||||
this.setState({ panelsPerRow: Number.isInteger(perRow) ? perRow : undefined });
|
||||
}
|
||||
}
|
||||
|
||||
public onEnterEditMode = (fromExplore = false) => {
|
||||
this._fromExplore = fromExplore;
|
||||
// Save this state
|
||||
@ -674,6 +695,19 @@ export class DashboardVariableDependency implements SceneVariableDependencyConfi
|
||||
appEvents.publish(new VariablesChanged({ refreshAll: true, panelIds: [] }));
|
||||
}
|
||||
|
||||
if (variable.state.name === PANEL_SEARCH_VAR) {
|
||||
const searchValue = variable.getValue();
|
||||
if (typeof searchValue === 'string') {
|
||||
this._dashboard.setState({ panelSearch: searchValue });
|
||||
}
|
||||
} else if (variable.state.name === PANELS_PER_ROW_VAR) {
|
||||
const panelsPerRow = variable.getValue();
|
||||
if (typeof panelsPerRow === 'string') {
|
||||
const perRow = Number.parseInt(panelsPerRow, 10);
|
||||
this._dashboard.setState({ panelsPerRow: Number.isInteger(perRow) ? perRow : undefined });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate variable changes to repeat row behavior as it does not get it when it's nested under local value
|
||||
* The first repeated row has the row repeater behavior but it also has a local SceneVariableSet with a local variable value
|
||||
|
@ -15,9 +15,11 @@ import { useSelector } from 'app/types';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { NavToolbarActions } from './NavToolbarActions';
|
||||
import { PanelSearchLayout } from './PanelSearchLayout';
|
||||
|
||||
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
|
||||
const { controls, overlay, editview, editPanel, isEmpty, meta, viewPanelScene } = model.useState();
|
||||
const { controls, overlay, editview, editPanel, isEmpty, meta, viewPanelScene, panelSearch, panelsPerRow } =
|
||||
model.useState();
|
||||
const headerHeight = useChromeHeaderHeight();
|
||||
const styles = useStyles2(getStyles, headerHeight);
|
||||
const location = useLocation();
|
||||
@ -63,13 +65,16 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
||||
|
||||
const notFound = meta.dashboardNotFound && <EntityNotFound entity="Dashboard" key="dashboard-not-found" />;
|
||||
|
||||
let body = [withPanels];
|
||||
let body: React.ReactNode = [withPanels];
|
||||
|
||||
if (notFound) {
|
||||
body = [notFound];
|
||||
} else if (isEmpty) {
|
||||
body = [emptyState, withPanels];
|
||||
} else if (panelSearch || panelsPerRow) {
|
||||
body = <PanelSearchLayout panelSearch={panelSearch} panelsPerRow={panelsPerRow} dashboard={model} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||
{editPanel && <editPanel.Component model={editPanel} />}
|
||||
|
@ -0,0 +1,73 @@
|
||||
import { css } from '@emotion/css';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneGridLayout, VizPanel, sceneGraph } from '@grafana/scenes';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { activateInActiveParents } from '../utils/utils';
|
||||
|
||||
import { DashboardGridItem } from './DashboardGridItem';
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardScene;
|
||||
panelSearch?: string;
|
||||
panelsPerRow?: number;
|
||||
}
|
||||
|
||||
const panelsPerRowCSSVar = '--panels-per-row';
|
||||
|
||||
export function PanelSearchLayout({ dashboard, panelSearch = '', panelsPerRow }: Props) {
|
||||
const { body } = dashboard.state;
|
||||
const panels: VizPanel[] = [];
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!(body instanceof SceneGridLayout)) {
|
||||
return <Trans i18nKey="panel-search.unsupported-layout">Unsupported layout</Trans>;
|
||||
}
|
||||
|
||||
for (const gridItem of body.state.children) {
|
||||
if (gridItem instanceof DashboardGridItem) {
|
||||
const panel = gridItem.state.body;
|
||||
const interpolatedTitle = sceneGraph.interpolate(dashboard, panel.state.title).toLowerCase();
|
||||
const interpolatedSearchString = sceneGraph.interpolate(dashboard, panelSearch).toLowerCase();
|
||||
if (interpolatedTitle.includes(interpolatedSearchString)) {
|
||||
panels.push(gridItem.state.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.grid, { [styles.perRow]: panelsPerRow !== undefined })}
|
||||
style={{ [panelsPerRowCSSVar]: panelsPerRow } as Record<string, number>}
|
||||
>
|
||||
{panels.map((panel) => (
|
||||
<PanelSearchHit key={panel.state.key} panel={panel} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PanelSearchHit({ panel }: { panel: VizPanel }) {
|
||||
useEffect(() => activateInActiveParents(panel), [panel]);
|
||||
|
||||
return <panel.Component model={panel} />;
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
grid: css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
gap: theme.spacing(1),
|
||||
gridAutoRows: '320px',
|
||||
}),
|
||||
perRow: css({
|
||||
gridTemplateColumns: `repeat(var(${panelsPerRowCSSVar}, 3), 1fr)`,
|
||||
}),
|
||||
};
|
||||
}
|
@ -1900,6 +1900,9 @@
|
||||
"view": "View"
|
||||
}
|
||||
},
|
||||
"panel-search": {
|
||||
"unsupported-layout": "Unsupported layout"
|
||||
},
|
||||
"playlist-edit": {
|
||||
"error-prefix": "Error loading playlist:",
|
||||
"form": {
|
||||
|
@ -1900,6 +1900,9 @@
|
||||
"view": "Vįęŵ"
|
||||
}
|
||||
},
|
||||
"panel-search": {
|
||||
"unsupported-layout": "Ůʼnşūppőřŧęđ ľäyőūŧ"
|
||||
},
|
||||
"playlist-edit": {
|
||||
"error-prefix": "Ēřřőř ľőäđįʼnģ pľäyľįşŧ:",
|
||||
"form": {
|
||||
|
Loading…
Reference in New Issue
Block a user