Skip to content

Commit

Permalink
Added attributes breakdown tab
Browse files Browse the repository at this point in the history
  • Loading branch information
adrapereira committed Mar 12, 2024
1 parent b91b488 commit e4511d9
Show file tree
Hide file tree
Showing 8 changed files with 462 additions and 4 deletions.
51 changes: 51 additions & 0 deletions src/components/Explore/AddToFiltersGraphAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';

import { DataFrame } from '@grafana/data';
import {
SceneObjectState,
SceneObjectBase,
SceneComponentProps,
sceneGraph,
AdHocFiltersVariable,
} from '@grafana/scenes';
import { Button } from '@grafana/ui';

export interface AddToFiltersGraphActionState extends SceneObjectState {
frame: DataFrame;
variableName: string;
}

export class AddToFiltersGraphAction extends SceneObjectBase<AddToFiltersGraphActionState> {
public onClick = () => {
const variable = sceneGraph.lookupVariable(this.state.variableName, this);
if (!(variable instanceof AdHocFiltersVariable)) {
return;
}

const labels = this.state.frame.fields[1]?.labels ?? {};
if (Object.keys(labels).length !== 1) {
return;
}

const labelName = Object.keys(labels)[0];

variable.setState({
filters: [
...variable.state.filters,
{
key: labelName,
operator: '=',
value: labels[labelName],
},
],
});
};

public static Component = ({ model }: SceneComponentProps<AddToFiltersGraphAction>) => {
return (
<Button variant="primary" size="sm" fill="text" onClick={model.onClick}>
Add to filters
</Button>
);
};
}
87 changes: 87 additions & 0 deletions src/components/Explore/BreakdownLabelSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { css } from '@emotion/css';
import { useResizeObserver } from '@react-aria/utils';
import React, { useEffect, useRef, useState } from 'react';

import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Select, RadioButtonGroup, useStyles2, useTheme2, measureText } from '@grafana/ui';

type Props = {
options: Array<SelectableValue<string>>;
value?: string;
onChange: (label: string | undefined) => void;
};

const mainAttributes = ['name', 'rootName', 'rootServiceName', 'status', 'span.http.status_code'];

export function BreakdownLabelSelector({ options, value, onChange }: Props) {
const styles = useStyles2(getStyles);
const theme = useTheme2();

const [labelSelectorRequiredWidth, setLabelSelectorRequiredWidth] = useState<number>(0);
const [availableWidth, setAvailableWidth] = useState<number>(0);

const useHorizontalLabelSelector = availableWidth > labelSelectorRequiredWidth;

const controlsContainer = useRef<HTMLDivElement>(null);

useResizeObserver({
ref: controlsContainer,
onResize: () => {
const element = controlsContainer.current;
if (element) {
setAvailableWidth(element.clientWidth);
}
},
});

const mainOptions = mainAttributes
.filter((at) => !!options.find((op) => op.value === at))
.map((attribute) => ({ label: attribute, text: attribute, value: attribute }));

const otherOptions = options.filter((op) => !mainAttributes.includes(op.value?.toString()!));

useEffect(() => {
const { fontSize } = theme.typography;
const text = mainOptions.map((option) => option.label || option.text || '').join(' ');
const textWidth = measureText(text, fontSize).width;
const additionalWidthPerItem = 70;
setLabelSelectorRequiredWidth(textWidth + additionalWidthPerItem * mainOptions.length);
}, [mainOptions, theme]);

return (
<div ref={controlsContainer} className={styles.container}>
{useHorizontalLabelSelector ? (
<>
<RadioButtonGroup {...{ options: mainOptions, value, onChange }} />
<Select
{...{ value }}
placeholder={'Other attributes'}
options={otherOptions}
onChange={(selected) => onChange(selected.value)}
className={styles.select}
/>
</>
) : (
<Select
{...{ value }}
placeholder={'Select attribute'}
options={options}
onChange={(selected) => onChange(selected.value)}
className={styles.select}
/>
)}
</div>
);
}

function getStyles(theme: GrafanaTheme2) {
return {
select: css({
maxWidth: theme.spacing(22),
}),
container: css({
display: 'flex',
gap: theme.spacing(1),
}),
};
}
42 changes: 42 additions & 0 deletions src/components/Explore/LayoutSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { SelectableValue } from '@grafana/data';
import { SceneComponentProps, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { Field, RadioButtonGroup } from '@grafana/ui';

export interface LayoutSwitcherState extends SceneObjectState {
active: LayoutType;
layouts: SceneObject[];
options: Array<SelectableValue<LayoutType>>;
}

export type LayoutType = 'single' | 'grid' | 'rows';

export class LayoutSwitcher extends SceneObjectBase<LayoutSwitcherState> {
public Selector({ model }: { model: LayoutSwitcher }) {
const { active, options } = model.useState();

return (
<Field label="View">
<RadioButtonGroup options={options} value={active} onChange={model.onLayoutChange} />
</Field>
);
}

public onLayoutChange = (active: LayoutType) => {
this.setState({ active });
};

public static Component = ({ model }: SceneComponentProps<LayoutSwitcher>) => {
const { layouts, options, active } = model.useState();

const index = options.findIndex((o) => o.value === active);
if (index === -1) {
return null;
}

const layout = layouts[index];

return <layout.Component model={layout} />;
};
}
Loading

0 comments on commit e4511d9

Please sign in to comment.