mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore Metrics: Update history breadcrumb tooltips (#90825)
* add history handler * move them into functions * handle adding new history steps * handle time history by respecting the timezone * remove commented code * no type casting * add unit tests * add colons and a new type metric_page * remove console * fix unit tests
This commit is contained in:
parent
2fe506d502
commit
8dd6bfef3c
@ -52,8 +52,8 @@ describe('DataTrail', () => {
|
||||
expect(trail.state.topScene).toBeInstanceOf(MetricSelectScene);
|
||||
});
|
||||
|
||||
it('Should set history current step to 0', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(0);
|
||||
it('Should set history current step to 1', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(1);
|
||||
});
|
||||
|
||||
it('Should set history step 0 parentIndex to -1', () => {
|
||||
@ -75,11 +75,11 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('should add history step', () => {
|
||||
expect(trail.state.history.state.steps[1].type).toBe('metric');
|
||||
expect(trail.state.history.state.steps[1].type).toBe('metric_page');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 1', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(1);
|
||||
it('Should set history currentStep to 2', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(2);
|
||||
});
|
||||
|
||||
it('Should set history step 1 parentIndex to 0', () => {
|
||||
@ -105,11 +105,11 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('should add history step', () => {
|
||||
expect(trail.state.history.state.steps[2].type).toBe('time');
|
||||
expect(trail.state.history.state.steps[3].type).toBe('time');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 2', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(2);
|
||||
it('Should set history currentStep to 3', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(3);
|
||||
});
|
||||
|
||||
it('Should set history step 2 parentIndex to 1', () => {
|
||||
@ -125,7 +125,7 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('Current history step should have new `from` of "now-1h"', () => {
|
||||
expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
expect(trail.state.history.state.steps[3].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
});
|
||||
|
||||
describe('And when traversing back to step 1', () => {
|
||||
@ -154,12 +154,12 @@ describe('DataTrail', () => {
|
||||
expect(trail.state.history.state.steps[3].type).toBe('time');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 3', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(3);
|
||||
it('Should set history currentStep to 4', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(4);
|
||||
});
|
||||
|
||||
it('Should set history step 3 parentIndex to 1', () => {
|
||||
expect(trail.state.history.state.steps[3].parentIndex).toBe(1);
|
||||
it('Should set history step 4 parentIndex to 1', () => {
|
||||
expect(trail.state.history.state.steps[4].parentIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('Should have time range `from` be updated "now-15m"', () => {
|
||||
@ -171,7 +171,7 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('History step 2 should still have `from` of "now-1h"', () => {
|
||||
expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
expect(trail.state.history.state.steps[3].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
});
|
||||
|
||||
describe('And then when returning again to step 1', () => {
|
||||
@ -191,12 +191,12 @@ describe('DataTrail', () => {
|
||||
expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-6h');
|
||||
});
|
||||
|
||||
it('History step 2 should still have `from` of "now-1h"', () => {
|
||||
expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
it('History step 3 should still have `from` of "now-1h"', () => {
|
||||
expect(trail.state.history.state.steps[3].trailState.$timeRange?.state.from).toBe('now-1h');
|
||||
});
|
||||
|
||||
it('History step 3 should still have `from` of "now-15m"', () => {
|
||||
expect(trail.state.history.state.steps[3].trailState.$timeRange?.state.from).toBe('now-15m');
|
||||
it('History step 4 should still have `from` of "now-15m"', () => {
|
||||
expect(trail.state.history.state.steps[4].trailState.$timeRange?.state.from).toBe('now-15m');
|
||||
});
|
||||
|
||||
it('Should have time range `from` be set back to "now-6h"', () => {
|
||||
@ -217,11 +217,11 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('should add history step', () => {
|
||||
expect(trail.state.history.state.steps[2].type).toBe('filters');
|
||||
expect(trail.state.history.state.steps[3].type).toBe('filters');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 2', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(2);
|
||||
it('Should set history currentStep to 3', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(3);
|
||||
});
|
||||
|
||||
it('Should set history step 2 parentIndex to 1', () => {
|
||||
@ -238,8 +238,8 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('Current history step should have new filter zone=a', () => {
|
||||
expect(getStepFilterVar(2).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(2).state.filters[0].value).toBe('a');
|
||||
expect(getStepFilterVar(3).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(3).state.filters[0].value).toBe('a');
|
||||
});
|
||||
|
||||
describe('And when traversing back to step 1', () => {
|
||||
@ -268,12 +268,12 @@ describe('DataTrail', () => {
|
||||
expect(trail.state.history.state.steps[3].type).toBe('filters');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 3', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(3);
|
||||
it('Should set history currentStep to 4', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(4);
|
||||
});
|
||||
|
||||
it('Should set history step 3 parentIndex to 1', () => {
|
||||
expect(trail.state.history.state.steps[3].parentIndex).toBe(1);
|
||||
it('Should set history step 4 parentIndex to 1', () => {
|
||||
expect(trail.state.history.state.steps[4].parentIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('Should have filter be updated to "zone=b"', () => {
|
||||
@ -285,14 +285,14 @@ describe('DataTrail', () => {
|
||||
expect(getStepFilterVar(1).state.filters.length).toBe(0);
|
||||
});
|
||||
|
||||
it('History step 2 should still have old filter zone=a', () => {
|
||||
expect(getStepFilterVar(2).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(2).state.filters[0].value).toBe('a');
|
||||
it('History step 3 should still have old filter zone=a', () => {
|
||||
expect(getStepFilterVar(3).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(3).state.filters[0].value).toBe('a');
|
||||
});
|
||||
|
||||
it('Current history step 3 should have new filter zone=b', () => {
|
||||
expect(getStepFilterVar(3).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(3).state.filters[0].value).toBe('b');
|
||||
it('Current history step 4 should have new filter zone=b', () => {
|
||||
expect(getStepFilterVar(4).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(4).state.filters[0].value).toBe('b');
|
||||
});
|
||||
|
||||
describe('And then when returning again to step 1', () => {
|
||||
@ -316,14 +316,14 @@ describe('DataTrail', () => {
|
||||
expect(getStepFilterVar(1).state.filters.length).toBe(0);
|
||||
});
|
||||
|
||||
it('History step 2 should still have old filter zone=a', () => {
|
||||
expect(getStepFilterVar(2).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(2).state.filters[0].value).toBe('a');
|
||||
it('History step 3 should still have old filter zone=a', () => {
|
||||
expect(getStepFilterVar(3).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(3).state.filters[0].value).toBe('a');
|
||||
});
|
||||
|
||||
it('History step 3 should have new filter zone=b', () => {
|
||||
expect(getStepFilterVar(3).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(3).state.filters[0].value).toBe('b');
|
||||
it('History step 4 should have new filter zone=b', () => {
|
||||
expect(getStepFilterVar(4).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(4).state.filters[0].value).toBe('b');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -331,11 +331,11 @@ describe('DataTrail', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('When going back to history step 1', () => {
|
||||
describe('When going back to history step 2', () => {
|
||||
beforeEach(() => {
|
||||
trail.publishEvent(new MetricSelectedEvent('first_metric'));
|
||||
trail.publishEvent(new MetricSelectedEvent('second_metric'));
|
||||
trail.state.history.goBackToStep(1);
|
||||
trail.state.history.goBackToStep(2);
|
||||
});
|
||||
|
||||
it('Should restore state and url', () => {
|
||||
@ -343,12 +343,12 @@ describe('DataTrail', () => {
|
||||
expect(locationService.getSearchObject().metric).toBe('first_metric');
|
||||
});
|
||||
|
||||
it('Should set history currentStep to 1', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(1);
|
||||
it('Should set history currentStep to 2', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(2);
|
||||
});
|
||||
|
||||
it('Should not create another history step', () => {
|
||||
expect(trail.state.history.state.steps.length).toBe(3);
|
||||
expect(trail.state.history.state.steps.length).toBe(4);
|
||||
});
|
||||
|
||||
describe('But then selecting a new metric', () => {
|
||||
@ -357,15 +357,15 @@ describe('DataTrail', () => {
|
||||
});
|
||||
|
||||
it('Should create another history step', () => {
|
||||
expect(trail.state.history.state.steps.length).toBe(4);
|
||||
expect(trail.state.history.state.steps.length).toBe(5);
|
||||
});
|
||||
|
||||
it('Should set history current step to 3', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(3);
|
||||
it('Should set history current step to 4', () => {
|
||||
expect(trail.state.history.state.currentStep).toBe(4);
|
||||
});
|
||||
|
||||
it('Should set history step 3 parent index to 1', () => {
|
||||
expect(trail.state.history.state.steps[3].parentIndex).toBe(1);
|
||||
it('Should set history step 4 parent index to 2', () => {
|
||||
expect(trail.state.history.state.steps[4].parentIndex).toBe(2);
|
||||
});
|
||||
|
||||
describe('And browser back button is pressed', () => {
|
||||
@ -407,9 +407,9 @@ describe('DataTrail', () => {
|
||||
expect(getFilterVar().state.filters[0].value).toBe('a');
|
||||
});
|
||||
|
||||
it('Filter of step 1 should be zone=a', () => {
|
||||
expect(getStepFilterVar(1).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(1).state.filters[0].value).toBe('a');
|
||||
it('Filter of step 2 should be zone=a', () => {
|
||||
expect(getStepFilterVar(2).state.filters[0].key).toBe('zone');
|
||||
expect(getStepFilterVar(2).state.filters[0].value).toBe('a');
|
||||
});
|
||||
|
||||
it('Filter of step 0 should empty', () => {
|
||||
@ -440,12 +440,12 @@ describe('DataTrail', () => {
|
||||
expect(trail.state.$timeRange?.state.from).toBe('now-15m');
|
||||
});
|
||||
|
||||
it('Time range `from` of step 1 should be now-15m', () => {
|
||||
expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-15m');
|
||||
it('Time range `from` of step 2 should be now-15m', () => {
|
||||
expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-15m');
|
||||
});
|
||||
|
||||
it('Time range `from` of step 0 should be now-6h', () => {
|
||||
expect(trail.state.history.state.steps[0].trailState.$timeRange?.state.from).toBe('now-6h');
|
||||
it('Time range `from` of step 1 should be now-6h', () => {
|
||||
expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-6h');
|
||||
});
|
||||
|
||||
describe('When returning to step 0', () => {
|
||||
|
65
public/app/features/trails/DataTrailsHistory.test.tsx
Normal file
65
public/app/features/trails/DataTrailsHistory.test.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { SceneObjectUrlValues } from '@grafana/scenes';
|
||||
|
||||
import { parseFilterTooltip, parseTimeTooltip } from './DataTrailsHistory';
|
||||
|
||||
type ParseTimeTestCase = {
|
||||
name: string;
|
||||
input: SceneObjectUrlValues;
|
||||
expected: string;
|
||||
};
|
||||
|
||||
type ParseFilterTestCase = {
|
||||
name: string;
|
||||
input: { urlValues: SceneObjectUrlValues; filtersApplied: string[] };
|
||||
expected: string;
|
||||
expectedFiltersApplied: string[];
|
||||
};
|
||||
|
||||
describe('DataTrailsHistory', () => {
|
||||
describe('parseTimeTooltip', () => {
|
||||
// global timezone is set to Pacific/Easter, see jest-config.js file
|
||||
test.each<ParseTimeTestCase>([
|
||||
{
|
||||
name: 'from history',
|
||||
input: { from: '2024-07-22T18:30:00.000Z', to: '2024-07-22T19:30:00.000Z' },
|
||||
expected: '2024-07-22 12:30:00 - 2024-07-22 13:30:00',
|
||||
},
|
||||
{
|
||||
name: 'time change event with timezone',
|
||||
input: { from: '2024-07-22T18:30:00.000Z', to: '2024-07-22T19:30:00.000Z', timeZone: 'Europe/Berlin' },
|
||||
expected: '2024-07-22 20:30:00 - 2024-07-22 21:30:00',
|
||||
},
|
||||
])('$name', ({ input, expected }) => {
|
||||
const result = parseTimeTooltip(input);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseFilterTooltip', () => {
|
||||
test.each<ParseFilterTestCase>([
|
||||
{
|
||||
name: 'from history initial load',
|
||||
input: {
|
||||
urlValues: { 'var-filters': ['job|=|grafana'] },
|
||||
filtersApplied: [],
|
||||
},
|
||||
expected: 'job = grafana',
|
||||
expectedFiltersApplied: ['job|=|grafana'],
|
||||
},
|
||||
{
|
||||
name: 'from history initial load',
|
||||
input: {
|
||||
urlValues: { 'var-filters': ['job|=|grafana', 'instance|=|host.docker.internal:3000'] },
|
||||
filtersApplied: ['job|=|grafana'],
|
||||
},
|
||||
expected: 'instance = host.docker.internal:3000',
|
||||
expectedFiltersApplied: ['job|=|grafana', 'instance|=|host.docker.internal:3000'],
|
||||
},
|
||||
])('$name', ({ input, expected, expectedFiltersApplied }) => {
|
||||
const filtersApplied = input.filtersApplied;
|
||||
const result = parseFilterTooltip(input.urlValues, filtersApplied);
|
||||
expect(result).toBe(expected);
|
||||
expect(filtersApplied).toEqual(expectedFiltersApplied);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,19 +1,24 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { getTimeZoneInfo, GrafanaTheme2, InternalTimeZones, TIME_FORMAT } from '@grafana/data';
|
||||
import { convertRawToRange } from '@grafana/data/src/datetime/rangeutil';
|
||||
import {
|
||||
SceneObjectState,
|
||||
SceneObjectBase,
|
||||
getUrlSyncManager,
|
||||
SceneComponentProps,
|
||||
SceneVariableValueChangedEvent,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneObjectStateChangedEvent,
|
||||
SceneObjectUrlValue,
|
||||
SceneObjectUrlValues,
|
||||
SceneTimeRange,
|
||||
sceneUtils,
|
||||
SceneVariableValueChangedEvent,
|
||||
} from '@grafana/scenes';
|
||||
import { useStyles2, Tooltip, Stack } from '@grafana/ui';
|
||||
import { Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { DataTrail, DataTrailState, getTopSceneFor } from './DataTrail';
|
||||
import { SerializedTrailHistory } from './TrailStore/TrailStore';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import { VAR_FILTERS } from './shared';
|
||||
import { getTrailFor, isSceneTimeRangeState } from './utils';
|
||||
@ -21,23 +26,42 @@ import { getTrailFor, isSceneTimeRangeState } from './utils';
|
||||
export interface DataTrailsHistoryState extends SceneObjectState {
|
||||
currentStep: number;
|
||||
steps: DataTrailHistoryStep[];
|
||||
filtersApplied: string[];
|
||||
}
|
||||
|
||||
export function isDataTrailsHistoryState(state: SceneObjectState): state is DataTrailsHistoryState {
|
||||
return 'currentStep' in state && 'steps' in state;
|
||||
}
|
||||
|
||||
export function isDataTrailHistoryFilter(filter?: SceneObjectUrlValue): filter is string[] {
|
||||
return !!filter;
|
||||
}
|
||||
|
||||
const isString = (value: unknown): value is string => typeof value === 'string';
|
||||
|
||||
export interface DataTrailHistoryStep {
|
||||
description: string;
|
||||
detail: string;
|
||||
type: TrailStepType;
|
||||
trailState: DataTrailState;
|
||||
parentIndex: number;
|
||||
}
|
||||
|
||||
export type TrailStepType = 'filters' | 'time' | 'metric' | 'start';
|
||||
export type TrailStepType = 'filters' | 'time' | 'metric' | 'start' | 'metric_page';
|
||||
|
||||
const filterSubst = ` $2 `;
|
||||
const filterPipeRegex = /(\|)(=|=~|!=|>|<|!~)(\|)/g;
|
||||
const stepDescriptionMap: Record<TrailStepType, string> = {
|
||||
start: 'Start of history',
|
||||
metric: 'Metric selected:',
|
||||
metric_page: 'Metric select page',
|
||||
filters: 'Filter applied:',
|
||||
time: 'Time range changed:',
|
||||
};
|
||||
|
||||
export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
public constructor(state: Partial<DataTrailsHistoryState>) {
|
||||
super({ steps: state.steps ?? [], currentStep: state.currentStep ?? 0 });
|
||||
super({ steps: state.steps ?? [], currentStep: state.currentStep ?? 0, filtersApplied: [] });
|
||||
|
||||
this.addActivationHandler(this._onActivate.bind(this));
|
||||
}
|
||||
@ -62,7 +86,9 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
|
||||
// But must add a secondary step to represent the selection of the metric
|
||||
// for this restored trail state
|
||||
this.addTrailStep(trail, 'metric');
|
||||
this.addTrailStep(trail, 'metric', trail.state.metric);
|
||||
} else {
|
||||
this.addTrailStep(trail, 'metric_page');
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,15 +99,20 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
this.state.steps[0].trailState = sceneUtils.cloneSceneObjectState(oldState, { history: this });
|
||||
}
|
||||
|
||||
if (newState.metric || oldState.metric) {
|
||||
this.addTrailStep(trail, 'metric');
|
||||
if (!newState.metric) {
|
||||
this.addTrailStep(trail, 'metric_page');
|
||||
} else {
|
||||
this.addTrailStep(trail, 'metric', newState.metric);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
trail.subscribeToEvent(SceneVariableValueChangedEvent, (evt) => {
|
||||
if (evt.payload.state.name === VAR_FILTERS) {
|
||||
this.addTrailStep(trail, 'filters');
|
||||
const filtersApplied = this.state.filtersApplied;
|
||||
const urlState = getUrlSyncManager().getUrlState(trail);
|
||||
this.addTrailStep(trail, 'filters', parseFilterTooltip(urlState, filtersApplied));
|
||||
this.setState({ filtersApplied });
|
||||
}
|
||||
});
|
||||
|
||||
@ -93,14 +124,22 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
if (prevState.from === newState.from && prevState.to === newState.to) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.addTrailStep(trail, 'time');
|
||||
this.addTrailStep(
|
||||
trail,
|
||||
'time',
|
||||
parseTimeTooltip({
|
||||
from: newState.from,
|
||||
to: newState.to,
|
||||
timeZone: newState.timeZone,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addTrailStep(trail: DataTrail, type: TrailStepType) {
|
||||
public addTrailStep(trail: DataTrail, type: TrailStepType, detail = '') {
|
||||
if (this.stepTransitionInProgress) {
|
||||
// Do not add trail steps when step transition is in progress
|
||||
return;
|
||||
@ -114,8 +153,49 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
steps: [
|
||||
...this.state.steps,
|
||||
{
|
||||
description: 'Test',
|
||||
type,
|
||||
detail,
|
||||
description: stepDescriptionMap[type],
|
||||
trailState: sceneUtils.cloneSceneObjectState(trail.state, { history: this }),
|
||||
parentIndex,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public addTrailStepFromStorage(trail: DataTrail, step: SerializedTrailHistory) {
|
||||
if (this.stepTransitionInProgress) {
|
||||
// Do not add trail steps when step transition is in progress
|
||||
return;
|
||||
}
|
||||
|
||||
const type = step.type;
|
||||
const stepIndex = this.state.steps.length;
|
||||
const parentIndex = type === 'start' ? -1 : this.state.currentStep;
|
||||
const filtersApplied = this.state.filtersApplied;
|
||||
let detail = '';
|
||||
|
||||
switch (step.type) {
|
||||
case 'metric':
|
||||
detail = step.urlValues.metric?.toString() ?? '';
|
||||
break;
|
||||
case 'filters':
|
||||
detail = parseFilterTooltip(step.urlValues, filtersApplied);
|
||||
break;
|
||||
case 'time':
|
||||
detail = parseTimeTooltip(step.urlValues);
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
filtersApplied,
|
||||
currentStep: stepIndex,
|
||||
steps: [
|
||||
...this.state.steps,
|
||||
{
|
||||
type,
|
||||
detail,
|
||||
description: stepDescriptionMap[type],
|
||||
trailState: sceneUtils.cloneSceneObjectState(trail.state, { history: this }),
|
||||
parentIndex,
|
||||
},
|
||||
@ -145,8 +225,8 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
renderStepTooltip(step: DataTrailHistoryStep) {
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<div>{step.type}</div>
|
||||
{step.type === 'metric' && <div>{step.trailState.metric || 'Select new metric'}</div>}
|
||||
<div>{step.description}</div>
|
||||
{step.detail !== '' && <div>{step.detail}</div>}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@ -220,6 +300,43 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseTimeTooltip(urlValues: SceneObjectUrlValues): string {
|
||||
if (!isSceneTimeRangeState(urlValues)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const range = convertRawToRange({
|
||||
from: urlValues.from,
|
||||
to: urlValues.to,
|
||||
});
|
||||
|
||||
const zone = isString(urlValues.timeZone) ? urlValues.timeZone : InternalTimeZones.localBrowserTime;
|
||||
const tzInfo = getTimeZoneInfo(zone, Date.now());
|
||||
|
||||
const from = range.from.subtract(tzInfo?.offsetInMins ?? 0, 'minute').format(TIME_FORMAT);
|
||||
const to = range.to.subtract(tzInfo?.offsetInMins ?? 0, 'minute').format(TIME_FORMAT);
|
||||
|
||||
return `${from} - ${to}`;
|
||||
}
|
||||
|
||||
export function parseFilterTooltip(urlValues: SceneObjectUrlValues, filtersApplied: string[]): string {
|
||||
let detail = '';
|
||||
const varFilters = urlValues['var-filters'];
|
||||
if (isDataTrailHistoryFilter(varFilters)) {
|
||||
detail =
|
||||
varFilters.filter((f) => {
|
||||
if (f !== '' && !filtersApplied.includes(f)) {
|
||||
filtersApplied.push(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})[0] ?? '';
|
||||
}
|
||||
// filters saved as key|operator|value
|
||||
// we need to remove pipes (|)
|
||||
return detail.replace(filterPipeRegex, filterSubst);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
const visTheme = theme.visualization;
|
||||
|
||||
@ -290,6 +407,7 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
start: generateStepTypeStyle(visTheme.getColorByName('green')),
|
||||
filters: generateStepTypeStyle(visTheme.getColorByName('purple')),
|
||||
metric: generateStepTypeStyle(visTheme.getColorByName('orange')),
|
||||
metric_page: generateStepTypeStyle(visTheme.getColorByName('orange')),
|
||||
time: generateStepTypeStyle(theme.colors.primary.main),
|
||||
},
|
||||
};
|
||||
|
@ -14,13 +14,15 @@ import { createBookmarkSavedNotification } from './utils';
|
||||
|
||||
const MAX_RECENT_TRAILS = 20;
|
||||
|
||||
export interface SerializedTrailHistory {
|
||||
urlValues: SceneObjectUrlValues;
|
||||
type: TrailStepType;
|
||||
description: string;
|
||||
parentIndex: number;
|
||||
}
|
||||
|
||||
export interface SerializedTrail {
|
||||
history: Array<{
|
||||
urlValues: SceneObjectUrlValues;
|
||||
type: TrailStepType;
|
||||
description: string;
|
||||
parentIndex: number;
|
||||
}>;
|
||||
history: SerializedTrailHistory[];
|
||||
currentStep?: number; // Assume last step in history if not specified
|
||||
createdAt?: number;
|
||||
}
|
||||
@ -98,7 +100,7 @@ export class TrailStore {
|
||||
const parentIndex = step.parentIndex ?? trail.state.history.state.steps.length - 1;
|
||||
// Set the parent of the next trail step by setting the current step in history.
|
||||
trail.state.history.setState({ currentStep: parentIndex });
|
||||
trail.state.history.addTrailStep(trail, step.type);
|
||||
trail.state.history.addTrailStepFromStorage(trail, step);
|
||||
});
|
||||
|
||||
const currentStep = t.currentStep ?? trail.state.history.state.steps.length - 1;
|
||||
|
@ -100,7 +100,9 @@ export function getColorByIndex(index: number) {
|
||||
export type SceneTimeRangeState = SceneObjectState & {
|
||||
from: string;
|
||||
to: string;
|
||||
timeZone?: string;
|
||||
};
|
||||
|
||||
export function isSceneTimeRangeState(state: SceneObjectState): state is SceneTimeRangeState {
|
||||
const keys = Object.keys(state);
|
||||
return keys.includes('from') && keys.includes('to');
|
||||
|
Loading…
Reference in New Issue
Block a user