Skip to content

Commit

Permalink
Fields: Regex (#1023)
Browse files Browse the repository at this point in the history
* Fields: Regex - Supporting regex fields from Explore (#1023)
* Fields: Regex - Operator UI updates and double-quotes (#1028)
* Fields: Regex - Combining metadata and fields (#1029)
* Fields: Regex - custom multi-select levels variable  (#1030)

---------

Co-authored-by: Matias Chomicki <[email protected]>
  • Loading branch information
gtk-grafana and matyax authored Feb 5, 2025
1 parent 43b402c commit 47101b8
Show file tree
Hide file tree
Showing 39 changed files with 1,522 additions and 543 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"@grafana/data": "^11.3.0",
"@grafana/lezer-logql": "^0.2.7",
"@grafana/runtime": "^11.3.0",
"@grafana/scenes": "5.37.0",
"@grafana/scenes": "5.41.0",
"@grafana/ui": "^11.3.0",
"@hello-pangea/dnd": "^16.6.0",
"@lezer/common": "^1.2.1",
Expand Down
305 changes: 207 additions & 98 deletions src/Components/IndexScene/IndexScene.tsx

Large diffs are not rendered by default.

207 changes: 18 additions & 189 deletions src/Components/IndexScene/LayoutScene.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps, SceneFlexLayout, sceneGraph, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import React from 'react';
import { PatternControls } from './PatternControls';
import { AppliedPattern, IndexScene, IndexSceneState } from './IndexScene';
import { css, cx } from '@emotion/css';
import { GiveFeedbackButton } from './GiveFeedbackButton';
import { IndexScene } from './IndexScene';
import { css } from '@emotion/css';
import { InterceptBanner } from './InterceptBanner';

import { PLUGIN_ID } from '../../services/plugin';
import { CustomVariableValueSelectors } from './CustomVariableValueSelectors';
import { logger } from '../../services/logger';
import { LineFilterVariablesScene } from './LineFilterVariablesScene';
import { VariableLayoutScene } from './VariableLayoutScene';
import { LevelsVariableScene } from './LevelsVariableScene';

interface LayoutSceneState extends SceneObjectState {
interceptDismissed: boolean;
lineFilterRenderer?: LineFilterVariablesScene;
levelsRenderer?: LevelsVariableScene;
variableLayout?: SceneObject;
}

const interceptBannerStorageKey = `${PLUGIN_ID}.interceptBannerStorageKey`;

export const CONTROLS_VARS_FIRST_ROW_KEY = 'vars-row__datasource-labels-timepicker-button';
export const CONTROLS_VARS_METADATA_ROW_KEY = 'vars-metadata';
export const CONTROLS_VARS_FIELDS_ELSE_KEY = 'vars-all-else';
export const CONTROLS_VARS_LEVELS_ROW_KEY = 'vars-levels';
export const CONTROLS_VARS_FIELDS = 'vars-fields';
export const CONTROLS_VARS_FIELDS_COMBINED = 'vars-fields-metadata';
export const CONTROLS_VARS_TIMEPICKER = 'vars-timepicker';
export const CONTROLS_VARS_REFRESH = 'vars-refresh';
export const CONTROLS_VARS_TOOLBAR = 'vars-toolbar';
export const CONTROLS_VARS_DATASOURCE = 'vars-ds';
export const CONTROLS_VARS_LABELS = 'vars-labels';

export class LayoutScene extends SceneObjectBase<LayoutSceneState> {
constructor(state: Partial<LayoutSceneState>) {
Expand All @@ -39,8 +44,8 @@ export class LayoutScene extends SceneObjectBase<LayoutSceneState> {

static Component = ({ model }: SceneComponentProps<LayoutScene>) => {
const indexScene = sceneGraph.getAncestor(model, IndexScene);
const { controls, contentScene, patterns } = indexScene.useState();
const { interceptDismissed, lineFilterRenderer } = model.useState();
const { contentScene } = indexScene.useState();
const { interceptDismissed, variableLayout } = model.useState();

if (!contentScene) {
logger.warn('content scene not defined');
Expand All @@ -58,77 +63,8 @@ export class LayoutScene extends SceneObjectBase<LayoutSceneState> {
}}
/>
)}
<div className={styles.controlsContainer}>
<>
{/* First row - datasource, timepicker, refresh, labels, button */}
{controls && (
<div className={styles.controlsFirstRowContainer}>
<div className={styles.filtersWrap}>
<div className={cx(styles.filters, styles.firstRowWrapper)}>
{controls.map((control) => {
return control instanceof SceneFlexLayout ? (
<control.Component key={control.state.key} model={control} />
) : null;
})}
</div>
</div>
<div className={styles.controlsWrapper}>
<GiveFeedbackButton />
<div className={styles.controls}>
{controls.map((control) => {
return !(control instanceof CustomVariableValueSelectors) &&
!(control instanceof SceneFlexLayout) ? (
<control.Component key={control.state.key} model={control} />
) : null;
})}
</div>
</div>
</div>
)}

{/* Second row - Metadata */}
<div className={styles.controlsRowContainer}>
{controls &&
controls.map((control) => {
return control.state.key === CONTROLS_VARS_METADATA_ROW_KEY ? (
<div key={control.state.key} className={styles.filtersWrap}>
<div className={styles.filters}>
<control.Component model={control} />
</div>
</div>
) : null;
})}
</div>

{/* 3rd row - Patterns */}
<div className={styles.controlsRowContainer}>
<PatternControls
patterns={patterns}
onRemove={(patterns: AppliedPattern[]) => model.parent?.setState({ patterns } as IndexSceneState)}
/>
</div>

{/* 4th row - line filters */}
<div className={styles.controlsRowContainer}>
{lineFilterRenderer && <lineFilterRenderer.Component model={lineFilterRenderer} />}
</div>

{/* 5th row - Fields */}
<div className={styles.controlsRowContainer}>
{controls && (
<div className={styles.filtersWrap}>
<div className={styles.filters}>
{controls.map((control) => {
return control.state.key === CONTROLS_VARS_FIELDS_ELSE_KEY ? (
<control.Component key={control.state.key} model={control} />
) : null;
})}
</div>
</div>
)}
</div>
</>
</div>
{variableLayout && <variableLayout.Component model={variableLayout} />}

{/* Final "row" - body */}
<div className={styles.body}>{contentScene && <contentScene.Component model={contentScene} />}</div>
Expand All @@ -140,6 +76,8 @@ export class LayoutScene extends SceneObjectBase<LayoutSceneState> {
public onActivate() {
this.setState({
lineFilterRenderer: new LineFilterVariablesScene({}),
levelsRenderer: new LevelsVariableScene({}),
variableLayout: new VariableLayoutScene({}),
});
}

Expand All @@ -153,22 +91,6 @@ export class LayoutScene extends SceneObjectBase<LayoutSceneState> {

function getStyles(theme: GrafanaTheme2) {
return {
firstRowWrapper: css({
'& > div > div': {
gap: '16px',
label: 'first-row-wrapper',

[theme.breakpoints.down('lg')]: {
flexDirection: 'column',
},

// The datasource variable width should be auto, not fill the section
'& > div:first-child': {
flex: '1 0 auto',
display: 'inline-block',
},
},
}),
bodyContainer: css({
flexGrow: 1,
display: 'flex',
Expand All @@ -184,110 +106,17 @@ function getStyles(theme: GrafanaTheme2) {
maxWidth: '100vw',
}),
body: css({
label: 'body-wrapper',
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
}),
controlsFirstRowContainer: css({
label: 'controls-first-row',
display: 'flex',
gap: theme.spacing(2),
justifyContent: 'space-between',
alignItems: 'flex-start',
}),
controlsRowContainer: css({
'&:empty': {
display: 'none',
},
label: 'controls-row',
display: 'flex',
// @todo add custom renderers for all variables, this currently results in 2 "empty" rows that always take up space
gap: theme.spacing(1),
justifyContent: 'space-between',
alignItems: 'flex-start',
paddingLeft: theme.spacing(2),
}),
controlsContainer: css({
label: 'controlsContainer',
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
}),
filters: css({
label: 'filters',
display: 'flex',
}),
filtersWrap: css({
label: 'filtersWrap',
display: 'flex',
gap: theme.spacing(2),
width: 'calc(100% - 450)',
flexWrap: 'wrap',
alignItems: 'flex-end',
'& + div[data-testid="data-testid Dashboard template variables submenu Label Filters"]:empty': {
visibility: 'hidden',
},

//@todo not like this
// The filter variables container: i.e. services, filters
'& > div &:first-child': {
// The wrapper of each filter
'& > div': {
// The actual inputs container
'& > div': {
flexWrap: 'wrap',
// wrapper around all inputs
'& > div': {
maxWidth: '380px',

// Wrapper around each input: i.e. label name, binary operator, value
'& > div': {
// These inputs need to flex, otherwise the value takes all of available space and they look broken
flex: '1 0 auto',

// The value input needs to shrink when the parent component is at max width
'&:nth-child(3)': {
flex: '0 1 auto',
},
},
},
},
},
},
// the `service_name` filter is a special case where we want to hide the operator
'[data-testid="AdHocFilter-service_name"]': {
'div[class*="input-wrapper"]:first-child': {
display: 'none',
},
'div[class*="input-wrapper"]:nth-child(2)': {
marginLeft: 0,
},
},

['div >[title="Add filter"]']: {
border: 0,
display: 'none',
width: 0,
padding: 0,
margin: 0,
},
}),
controlsWrapper: css({
label: 'controlsWrapper',
display: 'flex',
flexDirection: 'column',
marginTop: theme.spacing(0.375),
}),
controls: css({
display: 'flex',
gap: theme.spacing(1),
}),
feedback: css({
textAlign: 'end',
}),
rotateIcon: css({
svg: { transform: 'rotate(180deg)' },
}),
};
}
Loading

0 comments on commit 47101b8

Please sign in to comment.