Skip to content

Commit 60dab67

Browse files
authored
feat(*): Revamp exploration type selector (#94)
1 parent 48acb88 commit 60dab67

File tree

4 files changed

+59
-90
lines changed

4 files changed

+59
-90
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ assignees: ''
88

99
### Bug description
1010

11-
- **Grafana version:**
12-
- **Plugin version:**
11+
- **Grafana version:**
12+
- **Plugin version:**
1313

1414
<!-- A clear and concise description of what the bug is and where you see it -->
1515
<!-- Please specify in which environment(s) you see it (cloud or open-source) -->

e2e/fixtures/pages/ExploreProfilesPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class ExploreProfilesPage extends PyroscopePage {
1414
}
1515

1616
async getSelectedExplorationType() {
17-
const label = await this.getExplorationTypeSelector().locator('input[checked] + label').textContent();
17+
const label = await this.getExplorationTypeSelector().locator('button[data-testid="is-active"]').textContent();
1818
return label?.trim();
1919
}
2020
}

src/pages/ProfilesExplorerView/components/SceneProfilesExplorer/SceneProfilesExplorer.tsx

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@ import {
1717
SplitLayout,
1818
} from '@grafana/scenes';
1919
import { IconButton, InlineLabel, useStyles2 } from '@grafana/ui';
20-
import { useResizeObserver } from '@react-aria/utils';
2120
import { displayError, displaySuccess } from '@shared/domain/displayStatus';
2221
import { reportInteraction } from '@shared/domain/reportInteraction';
2322
import { VersionInfoTooltip } from '@shared/ui/VersionInfoTooltip';
24-
import React, { useRef, useState } from 'react';
23+
import React from 'react';
2524

2625
import { SceneExploreAllServices } from '../../components/SceneExploreAllServices/SceneExploreAllServices';
2726
import { SceneExploreFavorites } from '../../components/SceneExploreFavorites/SceneExploreFavorites';
@@ -45,7 +44,7 @@ import { ScenePanelTypeSwitcher } from '../SceneByVariableRepeaterGrid/component
4544
import { SceneQuickFilter } from '../SceneByVariableRepeaterGrid/components/SceneQuickFilter';
4645
import { GridItemData } from '../SceneByVariableRepeaterGrid/types/GridItemData';
4746
import { SceneExploreServiceFlameGraph } from '../SceneExploreServiceFlameGraph/SceneExploreServiceFlameGraph';
48-
import { ExplorationTypeSelector, ExplorationTypeSelectorProps } from './ui/ExplorationTypeSelector';
47+
import { ExplorationTypeSelector } from './ui/ExplorationTypeSelector';
4948

5049
export interface SceneProfilesExplorerState extends Partial<EmbeddedSceneState> {
5150
explorationType?: ExplorationType;
@@ -87,6 +86,7 @@ export class SceneProfilesExplorer extends SceneObjectBase<SceneProfilesExplorer
8786
value: ExplorationType.FAVORITES,
8887
label: 'Favorites',
8988
description: 'Overview of favorited visualizations',
89+
icon: 'favorite',
9090
},
9191
];
9292

@@ -300,29 +300,6 @@ export class SceneProfilesExplorer extends SceneObjectBase<SceneProfilesExplorer
300300
} catch {}
301301
};
302302

303-
useExplorationTypeSelectorLayout = () => {
304-
const headerRef = useRef<HTMLDivElement>(null);
305-
const headerLeftRef = useRef<HTMLDivElement>(null);
306-
const headerRightRef = useRef<HTMLDivElement>(null);
307-
308-
const [layout, setLayout] = useState<ExplorationTypeSelectorProps['layout']>('radio');
309-
310-
const onResize = () => {
311-
const currentRight = headerRightRef.current?.getBoundingClientRect();
312-
setLayout(Math.ceil(currentRight?.left || 970) >= 970 ? 'radio' : 'select');
313-
};
314-
315-
useResizeObserver({ ref: headerRef, onResize });
316-
useResizeObserver({ ref: headerRightRef, onResize });
317-
318-
return {
319-
headerRef,
320-
headerLeftRef,
321-
headerRightRef,
322-
layout,
323-
};
324-
};
325-
326303
useProfilesExplorer = () => {
327304
const { explorationType, controls, body } = this.useState();
328305

@@ -334,25 +311,12 @@ export class SceneProfilesExplorer extends SceneObjectBase<SceneProfilesExplorer
334311
gridControls: Array<SceneObject & { key?: string }>;
335312
};
336313

337-
const {
338-
headerRef,
339-
headerLeftRef,
340-
headerRightRef,
341-
layout: explorationTypeSelectorLayout,
342-
} = this.useExplorationTypeSelectorLayout();
343-
344314
return {
345315
data: {
346316
explorationType,
347317
dataSourceVariable,
348318
timePickerControl,
349319
refreshPickerControl,
350-
headerRefs: {
351-
full: headerRef,
352-
left: headerLeftRef,
353-
right: headerRightRef,
354-
},
355-
explorationTypeSelectorLayout,
356320
sceneVariables,
357321
gridControls,
358322
body,
@@ -373,32 +337,29 @@ export class SceneProfilesExplorer extends SceneObjectBase<SceneProfilesExplorer
373337
dataSourceVariable,
374338
timePickerControl,
375339
refreshPickerControl,
376-
headerRefs,
377-
explorationTypeSelectorLayout,
378340
sceneVariables,
379341
gridControls,
380342
body,
381343
} = data;
382344

383345
return (
384346
<>
385-
<div ref={headerRefs.full} className={styles.header}>
347+
<div className={styles.header}>
386348
<div className={styles.controls}>
387-
<div ref={headerRefs.left} className={styles.headerLeft}>
349+
<div className={styles.headerLeft}>
388350
<div className={styles.dataSourceVariable}>
389351
<InlineLabel width="auto">{dataSourceVariable.state.label}</InlineLabel>
390352
<dataSourceVariable.Component model={dataSourceVariable} />
391353
</div>
392354

393355
<ExplorationTypeSelector
394-
layout={explorationTypeSelectorLayout}
395356
options={SceneProfilesExplorer.EXPLORATION_TYPE_OPTIONS}
396357
value={explorationType as string}
397358
onChange={actions.onChangeExplorationType}
398359
/>
399360
</div>
400361

401-
<div ref={headerRefs.right} className={styles.headerRight}>
362+
<div className={styles.headerRight}>
402363
<timePickerControl.Component key={timePickerControl.state.key} model={timePickerControl} />
403364
<refreshPickerControl.Component key={refreshPickerControl.state.key} model={refreshPickerControl} />
404365
<IconButton
@@ -447,24 +408,13 @@ const getStyles = (theme: GrafanaTheme2) => ({
447408
display: flex;
448409
gap: ${theme.spacing(1)};
449410
`,
450-
dataSourceVariable: css`
451-
display: flex;
452-
min-width: 160px;
453-
`,
454-
explorationTypeContainer: css`
455-
display: flex;
456-
`,
457-
explorationTypeRadio: css`
458-
display: flex;
459-
`,
460-
explorationTypeSelect: css`
461-
display: flex;
462-
min-width: 180px;
463-
`,
464411
headerRight: css`
465412
display: flex;
466413
gap: ${theme.spacing(1)};
467414
`,
415+
dataSourceVariable: css`
416+
display: flex;
417+
`,
468418
variable: css`
469419
display: flex;
470420
`,
Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,71 @@
1-
import { css } from '@emotion/css';
2-
import { SelectableValue } from '@grafana/data';
3-
import { InlineLabel, RadioButtonGroup, Select, useStyles2 } from '@grafana/ui';
1+
import { css, cx } from '@emotion/css';
2+
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
3+
import { Button, Icon, InlineLabel, useStyles2 } from '@grafana/ui';
4+
import { noOp } from '@shared/domain/noOp';
45
import React from 'react';
56

67
export type ExplorationTypeSelectorProps = {
7-
layout: 'radio' | 'select';
88
options: Array<SelectableValue<string>>;
99
value: string;
1010
onChange: (newValue: string) => void;
1111
};
1212

13-
export function ExplorationTypeSelector({ layout, options, value, onChange }: ExplorationTypeSelectorProps) {
13+
export function ExplorationTypeSelector({ options, value, onChange }: ExplorationTypeSelectorProps) {
1414
const styles = useStyles2(getStyles);
1515

1616
return (
1717
<div className={styles.explorationTypeContainer} data-testid="exploration-types">
18-
<InlineLabel width="auto">Exploration type</InlineLabel>
18+
<InlineLabel width="auto">Exploration</InlineLabel>
1919

20-
{layout === 'radio' ? (
21-
<RadioButtonGroup
22-
className={styles.explorationTypeRadio}
23-
options={options}
24-
value={value}
25-
fullWidth={false}
26-
onChange={onChange}
27-
/>
28-
) : (
29-
<Select
30-
className={styles.explorationTypeSelect}
31-
placeholder="Select a type"
32-
value={value}
33-
options={options}
34-
onChange={(option) => onChange(option.value!)}
35-
/>
36-
)}
20+
<div className={styles.breadcrumb}>
21+
{options.map((option, i) => {
22+
const isActive = value === option.value;
23+
return (
24+
<>
25+
<Button
26+
className={isActive ? cx(styles.button, styles.active) : styles.button}
27+
size="sm"
28+
icon={option.icon as any}
29+
variant={isActive ? 'primary' : 'secondary'}
30+
onClick={isActive ? noOp : () => onChange(option.value as string)}
31+
tooltip={option.description}
32+
tooltipPlacement="top"
33+
data-testid={isActive ? 'is-active' : undefined}
34+
>
35+
{option.label}
36+
</Button>
37+
38+
{i < options.length - 2 && <Icon name="arrow-right" />}
39+
</>
40+
);
41+
})}
42+
</div>
3743
</div>
3844
);
3945
}
4046

41-
const getStyles = () => ({
47+
const getStyles = (theme: GrafanaTheme2) => ({
4248
explorationTypeContainer: css`
4349
display: flex;
50+
align-items: center;
4451
`,
45-
explorationTypeRadio: css`
52+
breadcrumb: css`
53+
height: 32px;
54+
line-height: 32px;
4655
display: flex;
56+
align-items: center;
57+
58+
& > button:last-child {
59+
margin-left: ${theme.spacing(2)};
60+
}
4761
`,
48-
explorationTypeSelect: css`
49-
display: flex;
50-
min-width: 180px;
62+
button: css`
63+
height: 30px;
64+
line-height: 30px;
65+
`,
66+
active: css`
67+
&:hover {
68+
cursor: default;
69+
}
5170
`,
5271
});

0 commit comments

Comments
 (0)