Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(CompareView): Implement new Comparison view with Scenes #119

Merged
merged 86 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
41994a8
feat(Labels): Create new comparison flow
grafakus Aug 6, 2024
ebb8505
Merge branch 'main' into feat/revamp-labels
grafakus Aug 6, 2024
162e9d5
feat(ComparePanel): Personalized tooltips on radio buttons
grafakus Aug 6, 2024
62f551c
fix: GroupByVariable now loads when landing an favs then Labels with …
grafakus Aug 6, 2024
df0b747
fix(SceneExploreServiceLabels): The main timeseries should have a "Vi…
grafakus Aug 6, 2024
8edeec6
feat(ComparePanel): Remove radio button tooltip when selected
grafakus Aug 6, 2024
dfd2095
chore(SceneGroupByLabels): Prevent unwanted renderGridItems call
grafakus Aug 6, 2024
cc8c6aa
chore: Better comment
grafakus Aug 6, 2024
c6236ed
feat(*): Various improvements
grafakus Aug 7, 2024
f364e1c
feat(SceneLabelValuesGrid): Add "Hide panels without data" switcher
grafakus Aug 7, 2024
aeeba87
refactor: Slight change
grafakus Aug 7, 2024
309b725
feat(SceneLabelValuesGrid): Hide no data don't fetch data but render …
grafakus Aug 8, 2024
123205c
refactor(*): Various fixes and improvements
grafakus Aug 8, 2024
acf4b35
fix(SceneLabelValuesGrid): Add missing loading state change
grafakus Aug 8, 2024
136b3cd
feat(SceneMainServiceTimeseries): Preserve color when an item is prov…
grafakus Aug 8, 2024
08311d5
feat: Comment previous change
grafakus Aug 8, 2024
04f9265
refactor(*): Better naming and files org
grafakus Aug 9, 2024
d9bf0ee
feat(*): WiP
grafakus Aug 9, 2024
6eea745
feat(*): WiP
grafakus Aug 9, 2024
dab2dec
chore(*): Fix imports
grafakus Aug 9, 2024
db325a5
Merge branch 'feat/revamp-labels' into feat/new-scenes-comparison-view
grafakus Aug 9, 2024
90d2b8d
refactor(*): Better naming
grafakus Aug 9, 2024
25ff03d
fix(SceneComparePanel): Fix legend bug
grafakus Aug 9, 2024
0c655e7
fix: Small fixes
grafakus Aug 9, 2024
b7d3527
feat: Make selection work
grafakus Aug 9, 2024
4ce83a3
feat(*): Make toggle range selection mode work
grafakus Aug 9, 2024
d7d7b62
chore(StatsPanel): Small code improvement
grafakus Aug 12, 2024
83c315f
refactor(SceneStatsPanel): Better cohesion
grafakus Aug 12, 2024
0c25e6d
refactor(*): Prepare comparison flow improvement
grafakus Aug 12, 2024
1220b7f
chore(SceneLabelValuesTimeseries): Remove unused code
grafakus Aug 12, 2024
1be6afb
Merge branch 'refactor/prepare-revamp-labels' into feat/revamp-labels
grafakus Aug 12, 2024
082c211
chore(docs): Revert unwanted change
grafakus Aug 12, 2024
5fdf90d
chore: Remove unused code
grafakus Aug 12, 2024
5ead9e7
chore: Remove unused code
grafakus Aug 12, 2024
7aaee70
chore: Remove unused code
grafakus Aug 12, 2024
5e6d09d
chore(*): Small code improvements
grafakus Aug 12, 2024
f3c9b5c
Merge branch 'feat/revamp-labels' into feat/new-scenes-comparison-view
grafakus Aug 12, 2024
365644b
fix(*): Fix some imports
grafakus Aug 12, 2024
28aea56
refactor(*): Use native methods to retrieve Scene objects
grafakus Aug 13, 2024
aee5823
Merge branch 'refactor/prepare-revamp-labels' into feat/revamp-labels
grafakus Aug 13, 2024
d71d0e5
chore: ...
grafakus Aug 13, 2024
cc8e064
refactor(SceneLabelValuePanel): Early return
grafakus Aug 13, 2024
2a3fc04
Merge branch 'feat/revamp-labels' into feat/new-scenes-comparison-view
grafakus Aug 13, 2024
5ae1115
chore: Remove comment
grafakus Aug 13, 2024
3dfb791
feat(SceneVariableName): Reset filters when a different service is se…
grafakus Aug 13, 2024
a4764f8
chore(SwitchTimeRangeSelectionModeAction): Better tooltip
grafakus Aug 13, 2024
3861133
chore(*): Small TS improvements
grafakus Aug 13, 2024
50cb5a5
Merge branch 'refactor/prepare-revamp-labels' into feat/revamp-labels
grafakus Aug 13, 2024
3ed7070
Merge branch 'feat/revamp-labels' into feat/new-scenes-comparison-view
grafakus Aug 13, 2024
ffd1e0f
Merge branch 'main' into feat/new-scenes-comparison-view
grafakus Aug 14, 2024
0053dc5
feat(*): Fetch diff profile only when both annotations exist
grafakus Aug 16, 2024
24397a2
fix(SceneProfilesExplorer): Hide time and refresh pickers
grafakus Aug 16, 2024
269a77c
feat(SceneGroupLabels): Clicking on "Compare" opens the new view
grafakus Aug 16, 2024
a76bc31
feat(SceneComparePanel): Set default time range value = main time ra…
grafakus Aug 16, 2024
f7e30ef
feat(SceneExploreDiffFlameGraphs): Sync y-axis (raw version)
grafakus Aug 16, 2024
171ddb8
fix(SceneLabelValuesTimeseries): Update config only when new timeseri…
grafakus Aug 20, 2024
24a4748
refactor(*): Extract sync y axis as a behaviour + small fixes
grafakus Aug 20, 2024
01220c9
feat(*): sync flame graph time ranges with URL search params
grafakus Aug 20, 2024
190221a
feat(*): Use proper time range defaults
grafakus Aug 20, 2024
7786bf9
feat(Share): Update share link to use diff params as well
grafakus Aug 20, 2024
1898c1a
feat(SceneExploreDiffFlameGraphs): Slight improvement
grafakus Aug 20, 2024
31ce567
refactor(*): Various fixes & code improvements
grafakus Aug 20, 2024
c248ec9
chore(SceneExploreDiffFlameGraph): Add info banner
grafakus Aug 20, 2024
72101a6
fix(*): Time ranges sync and reset
grafakus Aug 21, 2024
1b1fe2c
refactor(*): Simplify code
grafakus Aug 21, 2024
d7c6578
chore: Remove comment
grafakus Aug 21, 2024
321ca9c
fix(*): Early return when no series have been received
grafakus Aug 21, 2024
2f3c45f
feat(Labels): Allow same item to be selected as baseline and comparison
grafakus Aug 22, 2024
2cf2194
fix(*): Support multiple data sources + reset annotations
grafakus Aug 22, 2024
54eb49b
refactor(ExplorationTypeSelector): DRY
grafakus Aug 22, 2024
2867c1b
refactor(syncYAxis): Simplify code
grafakus Aug 22, 2024
07d9ad5
chore: Add missing comments
grafakus Aug 22, 2024
a271e87
Merge branch 'main' into feat/new-scenes-comparison-view
grafakus Aug 22, 2024
d932ed6
chore(StatsPanel): minor UI tweaks
grafakus Aug 22, 2024
35de4c3
fix(SceneLabelValuePanel): Fix after main rebase
grafakus Aug 22, 2024
ea5b2ff
fix(CompareActions): Enable toggle on checkbox
grafakus Aug 22, 2024
f9791ff
fix(*): Small UI fixes
grafakus Aug 22, 2024
309c6f0
refactor(*): Create SceneDiffFlameGraph
grafakus Aug 22, 2024
f76037f
chore: Minor UI tweak
grafakus Aug 23, 2024
0ac31ee
chore(*): Address PR comments #1
grafakus Aug 23, 2024
98113a7
test(EndToEnd): Add smoke test for the new diff view
grafakus Aug 23, 2024
63b0732
chore(*): Address PR comments #2
grafakus Aug 23, 2024
5470a93
fix(Diff): Fix incorrect ranges + address PR comments
grafakus Aug 23, 2024
2b21e14
feat(Labels): Add tooltips on compare actions
grafakus Aug 23, 2024
c0e6740
chore: Better naming/clarify with comment
grafakus Aug 23, 2024
0e8ffa8
chore(SceneComparePanel): Minor UI fix
grafakus Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/tests/explore-profiles/explore-profiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test.describe('Explore Profiles', () => {
{ type: 'profiles', label: 'Profile types' },
{ type: 'labels', label: 'Labels' },
{ type: 'flame-graph', label: 'Flame graph' },
{ type: 'diff-flame-graph', label: 'Diff flame graph' },
{ type: 'favorites', label: 'Favorites' },
]) {
test(label, async ({ exploreProfilesPage }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { noOp } from '@shared/domain/noOp';
import { debounce, isEqual } from 'lodash';
import React from 'react';

import { EventDataReceived } from '../../domain/events/EventDataReceived';
import { EventTimeseriesDataReceived } from '../../domain/events/EventTimeseriesDataReceived';
grafakus marked this conversation as resolved.
Show resolved Hide resolved
import { FiltersVariable } from '../../domain/variables/FiltersVariable/FiltersVariable';
import { getSceneVariableValue } from '../../helpers/getSceneVariableValue';
import { SceneLabelValuesBarGauge } from '../SceneLabelValuesBarGauge';
Expand Down Expand Up @@ -315,8 +315,8 @@ export class SceneByVariableRepeaterGrid extends SceneObjectBase<SceneByVariable
}

setupHideNoData(vizPanel: SceneLabelValuesTimeseries | SceneLabelValuesBarGauge) {
const sub = vizPanel.subscribeToEvent(EventDataReceived, (event) => {
if (event.payload.series.length > 0) {
const sub = vizPanel.subscribeToEvent(EventTimeseriesDataReceived, (event) => {
if (event.payload.series?.length) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { css } from '@emotion/css';
import { DashboardCursorSync, GrafanaTheme2 } from '@grafana/data';
import { behaviors, SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import React from 'react';

import { ProfileMetricVariable } from '../../domain/variables/ProfileMetricVariable';
import { ServiceNameVariable } from '../../domain/variables/ServiceNameVariable';
import { CompareTarget } from '../SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/domain/types';
import { SceneComparePanel } from './components/SceneComparePanel/SceneComparePanel';
import { SceneDiffFlameGraph } from './components/SceneDiffFlameGraph/SceneDiffFlameGraph';
import { syncYAxis } from './domain/behaviours/syncYAxis';

interface SceneExploreDiffFlameGraphState extends SceneObjectState {
baselinePanel: SceneComparePanel;
comparisonPanel: SceneComparePanel;
body: SceneDiffFlameGraph;
}

export class SceneExploreDiffFlameGraph extends SceneObjectBase<SceneExploreDiffFlameGraphState> {
constructor({ useAncestorTimeRange }: { useAncestorTimeRange: boolean }) {
super({
key: 'explore-diff-flame-graph',
baselinePanel: new SceneComparePanel({
target: CompareTarget.BASELINE,
useAncestorTimeRange,
}),
comparisonPanel: new SceneComparePanel({
target: CompareTarget.COMPARISON,
useAncestorTimeRange,
}),
$behaviors: [
new behaviors.CursorSync({
key: 'metricCrosshairSync',
sync: DashboardCursorSync.Crosshair,
}),
syncYAxis(),
],
body: new SceneDiffFlameGraph(),
});

this.addActivationHandler(this.onActivate.bind(this));
}

onActivate() {
const profileMetricVariable = sceneGraph.findByKeyAndType(this, 'profileMetricId', ProfileMetricVariable);

profileMetricVariable.setState({ query: ProfileMetricVariable.QUERY_SERVICE_NAME_DEPENDENT });
profileMetricVariable.update(true);

return () => {
profileMetricVariable.setState({ query: ProfileMetricVariable.QUERY_DEFAULT });
profileMetricVariable.update(true);
};
}

// see SceneProfilesExplorer
getVariablesAndGridControls() {
return {
variables: [
sceneGraph.findByKeyAndType(this, 'serviceName', ServiceNameVariable),
sceneGraph.findByKeyAndType(this, 'profileMetricId', ProfileMetricVariable),
],
gridControls: [],
};
}

useDiffTimeRanges = () => {
const { baselinePanel, comparisonPanel } = this.state;

const { annotationTimeRange: baselineTimeRange } = baselinePanel.useDiffTimeRange();
const { annotationTimeRange: comparisonTimeRange } = comparisonPanel.useDiffTimeRange();

return {
baselineTimeRange,
comparisonTimeRange,
};
};

static Component({ model }: SceneComponentProps<SceneExploreDiffFlameGraph>) {
const styles = useStyles2(getStyles); // eslint-disable-line react-hooks/rules-of-hooks

const { baselinePanel, comparisonPanel, body } = model.useState();

return (
<div className={styles.container}>
<div className={styles.columns}>
<baselinePanel.Component model={baselinePanel} />
<comparisonPanel.Component model={comparisonPanel} />
</div>

<body.Component model={body} />
</div>
);
}
}

const getStyles = (theme: GrafanaTheme2) => ({
container: css`
width: 100%;
display: flex;
flex-direction: column;
`,
columns: css`
display: flex;
flex-direction: row;
gap: ${theme.spacing(1)};
margin-bottom: ${theme.spacing(1)};

& > div {
flex: 1 1 0;
}
`,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { css } from '@emotion/css';
import { FieldMatcherID, getValueFormat, GrafanaTheme2 } from '@grafana/data';
import {
SceneComponentProps,
SceneDataTransformer,
sceneGraph,
SceneObjectBase,
SceneObjectState,
SceneRefreshPicker,
SceneTimePicker,
SceneTimeRange,
SceneTimeRangeLike,
VariableDependencyConfig,
} from '@grafana/scenes';
import { InlineLabel, useStyles2 } from '@grafana/ui';
import { getProfileMetric, ProfileMetricId } from '@shared/infrastructure/profile-metrics/getProfileMetric';
import { omit } from 'lodash';
import React from 'react';

import { BASELINE_COLORS, COMPARISON_COLORS } from '../../../../../ComparisonView/ui/colors';
import { getDefaultTimeRange } from '../../../../domain/getDefaultTimeRange';
import { FiltersVariable } from '../../../../domain/variables/FiltersVariable/FiltersVariable';
import { getSceneVariableValue } from '../../../../helpers/getSceneVariableValue';
import { getSeriesStatsValue } from '../../../../infrastructure/helpers/getSeriesStatsValue';
import { getProfileMetricLabel } from '../../../../infrastructure/series/helpers/getProfileMetricLabel';
import { addRefId, addStats } from '../../../SceneByVariableRepeaterGrid/infrastructure/data-transformations';
import { CompareTarget } from '../../../SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/domain/types';
import { SceneLabelValuesTimeseries } from '../../../SceneLabelValuesTimeseries';
import {
SceneTimeRangeWithAnnotations,
TimeRangeWithAnnotationsMode,
} from './components/SceneTimeRangeWithAnnotations';
import {
SwitchTimeRangeSelectionModeAction,
TimerangeSelectionMode,
} from './domain/actions/SwitchTimeRangeSelectionModeAction';
import { EventSwitchTimerangeSelectionMode } from './domain/events/EventSwitchTimerangeSelectionMode';
import { buildCompareTimeSeriesQueryRunner } from './infrastructure/buildCompareTimeSeriesQueryRunner';

export interface SceneComparePanelState extends SceneObjectState {
target: CompareTarget;
filterKey: 'filtersBaseline' | 'filtersComparison';
title: string;
color: string;
timePicker: SceneTimePicker;
refreshPicker: SceneRefreshPicker;
$timeRange: SceneTimeRange;
timeseriesPanel: SceneLabelValuesTimeseries;
}

export class SceneComparePanel extends SceneObjectBase<SceneComparePanelState> {
protected _variableDependency = new VariableDependencyConfig(this, {
variableNames: ['profileMetricId'],
onReferencedVariableValueChanged: () => {
this.state.timeseriesPanel.updateTitle(this.buildTimeseriesTitle());
},
});

constructor({
target,
useAncestorTimeRange,
}: {
target: SceneComparePanelState['target'];
useAncestorTimeRange: boolean;
}) {
const filterKey = target === CompareTarget.BASELINE ? 'filtersBaseline' : 'filtersComparison';
const title = target === CompareTarget.BASELINE ? 'Baseline' : 'Comparison';
const color =
target === CompareTarget.BASELINE ? BASELINE_COLORS.COLOR.toString() : COMPARISON_COLORS.COLOR.toString();

super({
key: `${target}-panel`,
target,
filterKey,
title,
color,
$timeRange: new SceneTimeRange({ key: `${target}-panel-timerange`, ...getDefaultTimeRange() }),
timePicker: new SceneTimePicker({ isOnCanvas: true }),
refreshPicker: new SceneRefreshPicker({ isOnCanvas: true }),
timeseriesPanel: SceneComparePanel.buildTimeSeriesPanel({ target, filterKey, title, color }),
});

this.addActivationHandler(this.onActivate.bind(this, useAncestorTimeRange));
}

onActivate(useAncestorTimeRange: boolean) {
const { $timeRange, timeseriesPanel } = this.state;

if (useAncestorTimeRange) {
$timeRange.setState(omit(this.getAncestorTimeRange().state, 'key'));
}

timeseriesPanel.updateTitle(this.buildTimeseriesTitle());

const eventSub = this.subscribeToEvents();

return () => {
eventSub.unsubscribe();
};
}

static buildTimeSeriesPanel({ target, filterKey, title, color }: any) {
const timeseriesPanel = new SceneLabelValuesTimeseries({
item: {
index: 0,
value: target,
label: '',
queryRunnerParams: {},
},
data: new SceneDataTransformer({
$data: buildCompareTimeSeriesQueryRunner({ filterKey }),
transformations: [addRefId, addStats],
}),
overrides: (series) =>
series.map((s) => {
const metricField = s.fields[1];
const allValuesSum = getSeriesStatsValue(s, 'allValuesSum') || 0;
const formattedValue = getValueFormat(metricField.config.unit)(allValuesSum);
const displayName = `${title} total = ${formattedValue.text}${formattedValue.suffix}`;

return {
matcher: { id: FieldMatcherID.byFrameRefID, options: s.refId },
properties: [
{
id: 'displayName',
value: displayName,
},
{
id: 'color',
value: { mode: 'fixed', fixedColor: color },
},
],
};
}),
headerActions: () => [new SwitchTimeRangeSelectionModeAction()],
});

timeseriesPanel.state.body.setState({
$timeRange: new SceneTimeRangeWithAnnotations({
grafakus marked this conversation as resolved.
Show resolved Hide resolved
key: `${target}-annotation-timerange`,
mode: TimeRangeWithAnnotationsMode.ANNOTATIONS,
annotationColor:
target === CompareTarget.BASELINE ? BASELINE_COLORS.OVERLAY.toString() : COMPARISON_COLORS.OVERLAY.toString(),
annotationTitle: `${title} time range`,
}),
});

return timeseriesPanel;
}

protected getAncestorTimeRange(): SceneTimeRangeLike {
if (!this.parent || !this.parent.parent) {
throw new Error(typeof this + ' must be used within $timeRange scope');
}

return sceneGraph.getTimeRange(this.parent.parent);
}

subscribeToEvents() {
return this.subscribeToEvent(EventSwitchTimerangeSelectionMode, (event) => {
// this triggers a timeseries request to the API
// TODO: caching?
(this.state.timeseriesPanel.state.body.state.$timeRange as SceneTimeRangeWithAnnotations).setState({
mode:
event.payload.mode === TimerangeSelectionMode.FLAMEGRAPH
? TimeRangeWithAnnotationsMode.ANNOTATIONS
: TimeRangeWithAnnotationsMode.DEFAULT,
});
});
}

buildTimeseriesTitle() {
const profileMetricId = getSceneVariableValue(this, 'profileMetricId');
const { description } = getProfileMetric(profileMetricId as ProfileMetricId);
return description || getProfileMetricLabel(profileMetricId);
}

useDiffTimeRange() {
return (this.state.timeseriesPanel.state.body.state.$timeRange as SceneTimeRangeWithAnnotations).useState();
}

public static Component = ({ model }: SceneComponentProps<SceneComparePanel>) => {
const styles = useStyles2(getStyles);
const { title, timeseriesPanel: timeseries, timePicker, refreshPicker, filterKey } = model.useState();

const filtersVariable = sceneGraph.findByKey(model, filterKey) as FiltersVariable;

return (
<div className={styles.panel}>
<div className={styles.panelHeader}>
<h6>{title}</h6>

<div className={styles.timePicker}>
<timePicker.Component model={timePicker} />
<refreshPicker.Component model={refreshPicker} />
</div>
</div>

<div className={styles.filter}>
<InlineLabel width="auto">{filtersVariable.state.label}</InlineLabel>
<filtersVariable.Component model={filtersVariable} />
</div>

<div className={styles.timeseries}>{timeseries && <timeseries.Component model={timeseries} />}</div>
</div>
);
};
}

const getStyles = (theme: GrafanaTheme2) => ({
panel: css`
background-color: ${theme.colors.background.primary};
padding: ${theme.spacing(1)} ${theme.spacing(1)} 0 ${theme.spacing(1)};
border: 1px solid ${theme.colors.border.weak};
border-radius: 2px;
`,
panelHeader: css`
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: ${theme.spacing(2)};

& > h6 {
margin-top: -2px;
}
`,
timePicker: css`
display: flex;
justify-content: flex-end;
gap: ${theme.spacing(1)};
`,
filter: css`
display: flex;
margin-bottom: ${theme.spacing(3)};
`,
timeseries: css`
height: 200px;

& [data-viz-panel-key] > * {
border: 0 none;
}
`,
});
Loading
Loading