Skip to content

Commit

Permalink
Multiple include: Levels & Labels (#1074)
Browse files Browse the repository at this point in the history
* feat: Support multiple includes in value breakdowns

---------

Co-authored-by: Matias Chomicki <matyax@gmail.com>
  • Loading branch information
gtk-grafana and matyax authored Feb 25, 2025
1 parent 39f1110 commit 92072fa
Show file tree
Hide file tree
Showing 31 changed files with 1,863 additions and 431 deletions.
29 changes: 15 additions & 14 deletions src/Components/FilterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ type Props = {
include: string;
exclude: string;
};
hideExclude?: boolean
};

export const FilterButton = (props: Props) => {
const { isExcluded, isIncluded, onInclude, onExclude, onClear, titles, buttonFill } = props;
const styles = useStyles2(getStyles, isIncluded, isExcluded);
const { isExcluded, isIncluded, onInclude, onExclude, onClear, titles, buttonFill, hideExclude } = props;
const styles = useStyles2(getStyles, isIncluded, isExcluded, hideExclude);
return (
<div className={styles.container}>
<Button
Expand All @@ -34,31 +35,31 @@ export const FilterButton = (props: Props) => {
>
Include
</Button>
<Button
variant={isExcluded ? 'primary' : 'secondary'}
fill={buttonFill}
size="sm"
aria-selected={isExcluded}
className={styles.excludeButton}
onClick={isExcluded ? onClear : onExclude}
title={titles?.exclude}
data-testid={testIds.exploreServiceDetails.buttonFilterExclude}
{!hideExclude && <Button
variant={isExcluded ? 'primary' : 'secondary'}
fill={buttonFill}
size="sm"
aria-selected={isExcluded}
className={styles.excludeButton}
onClick={isExcluded ? onClear : onExclude}
title={titles?.exclude}
data-testid={testIds.exploreServiceDetails.buttonFilterExclude}
>
Exclude
</Button>
</Button>}
</div>
);
};

const getStyles = (theme: GrafanaTheme2, isIncluded: boolean, isExcluded: boolean) => {
const getStyles = (theme: GrafanaTheme2, isIncluded: boolean, isExcluded: boolean, hideExclude?: boolean) => {
return {
container: css({
display: 'flex',
justifyContent: 'center',
}),
includeButton: css({
borderRadius: 0,
borderRight: isIncluded ? undefined : 'none',
borderRight: isIncluded || hideExclude ? undefined : 'none',
}),
excludeButton: css({
borderRadius: `0 ${theme.shape.radius.default} ${theme.shape.radius.default} 0`,
Expand Down
75 changes: 10 additions & 65 deletions src/Components/IndexScene/LevelsVariableScene.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
ControlsLabel,
SceneComponentProps,
SceneObjectBase,
SceneObjectState,
SceneVariableValueChangedEvent,
} from '@grafana/scenes';
import { ControlsLabel, SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import React from 'react';
import { getLevelsVariable } from '../../services/variableGetters';
import { GrafanaTheme2, MetricFindValue, SelectableValue } from '@grafana/data';
Expand All @@ -31,21 +25,17 @@ export class LevelsVariableScene extends SceneObjectBase<LevelsVariableSceneStat

onActivate() {
this.onFilterChange();
const levelsVar = getLevelsVariable(this);
levelsVar.subscribeToEvent(SceneVariableValueChangedEvent, () => this.onFilterChange());
}

private onFilterChange() {
public onFilterChange() {
const levelsVar = getLevelsVariable(this);
if (levelsVar.state.filters.length) {
this.setState({
options: levelsVar.state.filters.map((filter) => ({
text: filter.valueLabels?.[0] ?? filter.value,
selected: true,
value: filter.value,
})),
});
}
this.setState({
options: levelsVar.state.filters.map((filter) => ({
text: filter.valueLabels?.[0] ?? filter.value,
selected: true,
value: filter.value,
})),
});
}

getTagValues = () => {
Expand Down Expand Up @@ -143,7 +133,7 @@ export class LevelsVariableScene extends SceneObjectBase<LevelsVariableSceneStat
hideSelectedOptions={false}
value={options?.filter((v) => v.selected)}
options={options?.map((val) => ({
value: val.text,
value: val.value,
label: val.text,
}))}
/>
Expand All @@ -156,49 +146,4 @@ const getStyles = (theme: GrafanaTheme2) => ({
flex: css({
flex: '1',
}),
removeButton: css({
marginInline: theme.spacing(0.5),
cursor: 'pointer',
'&:hover': {
color: theme.colors.text.primary,
},
}),
pillText: css({
maxWidth: '200px',
width: '100%',
textOverflow: 'ellipsis',
overflow: 'hidden',
}),
tooltipText: css({
textAlign: 'center',
}),
comboboxWrapper: css({
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'center',
columnGap: theme.spacing(1),
rowGap: theme.spacing(0.5),
minHeight: theme.spacing(4),
backgroundColor: theme.components.input.background,
border: `1px solid ${theme.colors.border.strong}`,
borderRadius: theme.shape.radius.default,
paddingInline: theme.spacing(1),
paddingBlock: theme.spacing(0.5),
flexGrow: 1,
}),
comboboxFocusOutline: css({
'&:focus-within': {
outline: '2px dotted transparent',
outlineOffset: '2px',
boxShadow: `0 0 0 2px ${theme.colors.background.canvas}, 0 0 0px 4px ${theme.colors.primary.main}`,
transitionTimingFunction: `cubic-bezier(0.19, 1, 0.22, 1)`,
transitionDuration: '0.2s',
transitionProperty: 'outline, outline-offset, box-shadow',
zIndex: 2,
},
}),
filterIcon: css({
color: theme.colors.text.secondary,
alignSelf: 'center',
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('addToFilters and addAdHocFilter', () => {
it('allows to clear a filter', () => {
const lookupVariable = jest.spyOn(sceneGraph, 'lookupVariable').mockReturnValue(adHocVariable);
jest.spyOn(sceneGraph, 'getAncestor').mockReturnValue(serviceScene);
addToFilters('existing', 'existingValue', 'clear', scene);
addToFilters('existing', 'existingValue', 'clear', scene, VAR_FIELDS);
expect(lookupVariable).toHaveBeenCalledWith(VAR_FIELDS_AND_METADATA, expect.anything());
expect(adHocVariable.state.filters).toEqual([]);
});
Expand Down Expand Up @@ -231,7 +231,7 @@ describe('addToFilters and addAdHocFilter', () => {
it.each(['=', '!='])('allows to add an %s filter', (operator: string) => {
const lookupVariable = jest.spyOn(sceneGraph, 'lookupVariable').mockReturnValue(adHocVariable);
jest.spyOn(sceneGraph, 'getAncestor').mockReturnValue(serviceScene);
addAdHocFilter({ key: 'key', value: 'value', operator }, scene);
addAdHocFilter({ key: 'key', value: 'value', operator }, scene, VAR_FIELDS);

expect(lookupVariable).toHaveBeenCalledWith(VAR_FIELDS_AND_METADATA, expect.anything());
expect(adHocVariable.state.filters).toEqual([
Expand Down
Loading

0 comments on commit 92072fa

Please sign in to comment.