mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
datatrails: detect if current trail state is bookmarked (#83283)
* fix: detect if current trail state is bookmarked
This commit is contained in:
@@ -86,6 +86,8 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
|||||||
const newStepWasAppended = newNumberOfSteps > oldNumberOfSteps;
|
const newStepWasAppended = newNumberOfSteps > oldNumberOfSteps;
|
||||||
|
|
||||||
if (newStepWasAppended) {
|
if (newStepWasAppended) {
|
||||||
|
// In order for the `useBookmarkState` to re-evaluate after a new step was made:
|
||||||
|
this.forceRender();
|
||||||
// Do nothing because the state is already up to date -- it created a new step!
|
// Do nothing because the state is already up to date -- it created a new step!
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DashboardCursorSync, GrafanaTheme2 } from '@grafana/data';
|
import { DashboardCursorSync, GrafanaTheme2 } from '@grafana/data';
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +26,7 @@ import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngin
|
|||||||
import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel';
|
import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel';
|
||||||
import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types';
|
import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types';
|
||||||
import { ShareTrailButton } from './ShareTrailButton';
|
import { ShareTrailButton } from './ShareTrailButton';
|
||||||
import { getTrailStore } from './TrailStore/TrailStore';
|
import { useBookmarkState } from './TrailStore/useBookmarkState';
|
||||||
import {
|
import {
|
||||||
ActionViewDefinition,
|
ActionViewDefinition,
|
||||||
ActionViewType,
|
ActionViewType,
|
||||||
@@ -151,14 +151,9 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
|||||||
const metricScene = sceneGraph.getAncestor(model, MetricScene);
|
const metricScene = sceneGraph.getAncestor(model, MetricScene);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const trail = getTrailFor(model);
|
const trail = getTrailFor(model);
|
||||||
const [isBookmarked, setBookmarked] = useState(false);
|
const [isBookmarked, toggleBookmark] = useBookmarkState(trail);
|
||||||
const { actionView } = metricScene.useState();
|
const { actionView } = metricScene.useState();
|
||||||
|
|
||||||
const onBookmarkTrail = () => {
|
|
||||||
getTrailStore().addBookmark(trail);
|
|
||||||
setBookmarked(!isBookmarked);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box paddingY={1}>
|
<Box paddingY={1}>
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
@@ -180,7 +175,7 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
tooltip={'Bookmark'}
|
tooltip={'Bookmark'}
|
||||||
onClick={onBookmarkTrail}
|
onClick={toggleBookmark}
|
||||||
/>
|
/>
|
||||||
{trail.state.embedded && (
|
{trail.state.embedded && (
|
||||||
<ToolbarButton variant={'canvas'} onClick={model.onOpenTrail}>
|
<ToolbarButton variant={'canvas'} onClick={model.onOpenTrail}>
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ describe('TrailStore', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Initialize store with one bookmark trail', () => {
|
describe('Initialize store with one bookmark trail', () => {
|
||||||
beforeAll(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
BOOKMARKED_TRAILS_KEY,
|
BOOKMARKED_TRAILS_KEY,
|
||||||
@@ -225,6 +225,35 @@ describe('TrailStore', () => {
|
|||||||
expect(store.recent.length).toBe(1);
|
expect(store.recent.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to obtain index of bookmark', () => {
|
||||||
|
const trail = store.bookmarks[0].resolve();
|
||||||
|
const index = store.getBookmarkIndex(trail);
|
||||||
|
expect(index).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('index should be undefined for removed bookmarks', () => {
|
||||||
|
const trail = store.bookmarks[0].resolve();
|
||||||
|
store.removeBookmark(0);
|
||||||
|
const index = store.getBookmarkIndex(trail);
|
||||||
|
expect(index).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('index should be undefined for a trail that has changed since it was bookmarked', () => {
|
||||||
|
const trail = store.bookmarks[0].resolve();
|
||||||
|
trail.setState({ metric: 'something_completely_different' });
|
||||||
|
const index = store.getBookmarkIndex(trail);
|
||||||
|
expect(index).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to obtain index of a bookmark for a trail that changed back to bookmarked state', () => {
|
||||||
|
const trail = store.bookmarks[0].resolve();
|
||||||
|
const bookmarkedMetric = trail.state.metric;
|
||||||
|
trail.setState({ metric: 'something_completely_different' });
|
||||||
|
trail.setState({ metric: bookmarkedMetric });
|
||||||
|
const index = store.getBookmarkIndex(trail);
|
||||||
|
expect(index).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should remove a bookmark', () => {
|
it('should remove a bookmark', () => {
|
||||||
expect(store.bookmarks.length).toBe(1);
|
expect(store.bookmarks.length).toBe(1);
|
||||||
store.removeBookmark(0);
|
store.removeBookmark(0);
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export class TrailStore {
|
|||||||
load() {
|
load() {
|
||||||
this._recent = this._loadFromStorage(RECENT_TRAILS_KEY);
|
this._recent = this._loadFromStorage(RECENT_TRAILS_KEY);
|
||||||
this._bookmarks = this._loadFromStorage(BOOKMARKED_TRAILS_KEY);
|
this._bookmarks = this._loadFromStorage(BOOKMARKED_TRAILS_KEY);
|
||||||
|
this._refreshBookmarkIndexMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
setRecentTrail(trail: DataTrail) {
|
setRecentTrail(trail: DataTrail) {
|
||||||
@@ -125,15 +126,47 @@ export class TrailStore {
|
|||||||
|
|
||||||
addBookmark(trail: DataTrail) {
|
addBookmark(trail: DataTrail) {
|
||||||
this._bookmarks.unshift(trail.getRef());
|
this._bookmarks.unshift(trail.getRef());
|
||||||
|
this._refreshBookmarkIndexMap();
|
||||||
this._save();
|
this._save();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBookmark(index: number) {
|
removeBookmark(index: number) {
|
||||||
if (index < this._bookmarks.length) {
|
if (index < this._bookmarks.length) {
|
||||||
this._bookmarks.splice(index, 1);
|
this._bookmarks.splice(index, 1);
|
||||||
|
this._refreshBookmarkIndexMap();
|
||||||
this._save();
|
this._save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBookmarkIndex(trail: DataTrail) {
|
||||||
|
const bookmarkKey = getBookmarkKey(trail);
|
||||||
|
const bookmarkIndex = this._bookmarkIndexMap.get(bookmarkKey);
|
||||||
|
return bookmarkIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _bookmarkIndexMap = new Map<string, number>();
|
||||||
|
|
||||||
|
private _refreshBookmarkIndexMap() {
|
||||||
|
this._bookmarkIndexMap.clear();
|
||||||
|
this._bookmarks.forEach((bookmarked, index) => {
|
||||||
|
const trail = bookmarked.resolve();
|
||||||
|
const key = getBookmarkKey(trail);
|
||||||
|
// If there are duplicate bookmarks, the latest index will be kept
|
||||||
|
this._bookmarkIndexMap.set(key, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBookmarkKey(trail: DataTrail) {
|
||||||
|
const urlState = getUrlSyncManager().getUrlState(trail);
|
||||||
|
// Not part of state
|
||||||
|
delete urlState.actionView;
|
||||||
|
// Populate defaults
|
||||||
|
if (urlState['var-groupby'] === '') {
|
||||||
|
urlState['var-groupby'] = '$__all';
|
||||||
|
}
|
||||||
|
const key = JSON.stringify(urlState);
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
let store: TrailStore | undefined;
|
let store: TrailStore | undefined;
|
||||||
|
|||||||
39
public/app/features/trails/TrailStore/useBookmarkState.ts
Normal file
39
public/app/features/trails/TrailStore/useBookmarkState.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { DataTrail } from '../DataTrail';
|
||||||
|
|
||||||
|
import { getTrailStore } from './TrailStore';
|
||||||
|
|
||||||
|
export function useBookmarkState(trail: DataTrail) {
|
||||||
|
// Note that trail object may stay the same, but the state used by `getBookmarkIndex` result may
|
||||||
|
// differ for each re-render of this hook
|
||||||
|
const getBookmarkIndex = () => getTrailStore().getBookmarkIndex(trail);
|
||||||
|
|
||||||
|
const indexOnRender = getBookmarkIndex();
|
||||||
|
|
||||||
|
const [bookmarkIndex, setBookmarkIndex] = useState(indexOnRender);
|
||||||
|
|
||||||
|
// Check if index changed and force a re-render
|
||||||
|
if (indexOnRender !== bookmarkIndex) {
|
||||||
|
setBookmarkIndex(indexOnRender);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBookmarked = bookmarkIndex != null;
|
||||||
|
|
||||||
|
const toggleBookmark = () => {
|
||||||
|
if (isBookmarked) {
|
||||||
|
let indexToRemove = getBookmarkIndex();
|
||||||
|
while (indexToRemove != null) {
|
||||||
|
// This loop will remove all indices that have an equivalent bookmark key
|
||||||
|
getTrailStore().removeBookmark(indexToRemove);
|
||||||
|
indexToRemove = getBookmarkIndex();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getTrailStore().addBookmark(trail);
|
||||||
|
}
|
||||||
|
setBookmarkIndex(getBookmarkIndex());
|
||||||
|
};
|
||||||
|
|
||||||
|
const result: [typeof isBookmarked, typeof toggleBookmark] = [isBookmarked, toggleBookmark];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user