From 2c7e95a680ee8bb041306b558d37e7e663114486 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Thu, 25 Jan 2024 20:06:22 +0000 Subject: [PATCH] Data Trails: Cleanup and explore+share buttons (#81062) * Share button works now. Removed add to dashboard button for now * WIP explore link * Remove settings dropdown for now * Use getExploreUrl to generate explore link * Fix conflicts * Update betterer * Navigate to a new trail when the recent trails list is empty * Address PR comments --- .betterer.results | 4 ++ public/app/features/trails/DataTrail.tsx | 3 +- public/app/features/trails/DataTrailCard.tsx | 12 +----- public/app/features/trails/DataTrailsHome.tsx | 10 ++++- public/app/features/trails/MetricScene.tsx | 43 ++++++++++++++++--- .../app/features/trails/ShareTrailButton.tsx | 28 ++++++++++++ public/app/features/trails/utils.ts | 10 ++++- 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 public/app/features/trails/ShareTrailButton.tsx diff --git a/.betterer.results b/.betterer.results index 3afaf609106..107bec63ffc 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4206,6 +4206,10 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "11"], [0, 0, 0, "Do not use any type assertions.", "12"] ], + "public/app/features/trails/MetricScene.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index e3aebb28ff6..0acb9ac4fc3 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -167,7 +167,7 @@ export class DataTrail extends SceneObjectBase { } static Component = ({ model }: SceneComponentProps) => { - const { controls, topScene, history, settings } = model.useState(); + const { controls, topScene, history } = model.useState(); const styles = useStyles2(getStyles); return ( @@ -178,7 +178,6 @@ export class DataTrail extends SceneObjectBase { {controls.map((control) => ( ))} - )}
{topScene && }
diff --git a/public/app/features/trails/DataTrailCard.tsx b/public/app/features/trails/DataTrailCard.tsx index 594579fae3e..77753c3c6ea 100644 --- a/public/app/features/trails/DataTrailCard.tsx +++ b/public/app/features/trails/DataTrailCard.tsx @@ -2,12 +2,12 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes'; import { useStyles2, Stack, Tooltip, Button } from '@grafana/ui'; import { DataTrail } from './DataTrail'; -import { LOGS_METRIC, VAR_DATASOURCE_EXPR, VAR_FILTERS } from './shared'; +import { LOGS_METRIC, VAR_FILTERS } from './shared'; +import { getDataSource, getDataSourceName } from './utils'; export interface Props { trail: DataTrail; @@ -67,14 +67,6 @@ function getMetricName(metric?: string) { return metric; } -function getDataSource(trail: DataTrail) { - return sceneGraph.interpolate(trail, VAR_DATASOURCE_EXPR); -} - -function getDataSourceName(dataSourceUid: string) { - return getDataSourceSrv().getInstanceSettings(dataSourceUid)?.name || dataSourceUid; -} - function getStyles(theme: GrafanaTheme2) { return { container: css({ diff --git a/public/app/features/trails/DataTrailsHome.tsx b/public/app/features/trails/DataTrailsHome.tsx index e186e2ffdf5..441a45d928f 100644 --- a/public/app/features/trails/DataTrailsHome.tsx +++ b/public/app/features/trails/DataTrailsHome.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/css'; import React, { useState } from 'react'; +import { Redirect } from 'react-router-dom'; import { GrafanaTheme2 } from '@grafana/data'; import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; @@ -10,7 +11,7 @@ import { DataTrail } from './DataTrail'; import { DataTrailCard } from './DataTrailCard'; import { DataTrailsApp } from './DataTrailsApp'; import { getTrailStore } from './TrailStore/TrailStore'; -import { getDatasourceForNewTrail, newMetricsTrail } from './utils'; +import { getDatasourceForNewTrail, getUrlForTrail, newMetricsTrail } from './utils'; export interface DataTrailsHomeState extends SceneObjectState {} @@ -43,6 +44,13 @@ export class DataTrailsHome extends SceneObjectBase { setLastDelete(Date.now()); // trigger re-render }; + // If there are no recent trails, don't show home page and create a new trail + if (!getTrailStore().recent.length) { + const trail = newMetricsTrail(getDatasourceForNewTrail()); + getTrailStore().setRecentTrail(trail); + return ; + } + return (
diff --git a/public/app/features/trails/MetricScene.tsx b/public/app/features/trails/MetricScene.tsx index 951897e2aa2..9b1b169e4d5 100644 --- a/public/app/features/trails/MetricScene.tsx +++ b/public/app/features/trails/MetricScene.tsx @@ -17,11 +17,14 @@ import { } from '@grafana/scenes'; import { ToolbarButton, Box, Stack, Icon, TabsBar, Tab, useStyles2 } from '@grafana/ui'; +import { getExploreUrl } from '../../core/utils/explore'; + import { buildBreakdownActionScene } from './ActionTabs/BreakdownScene'; import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene'; import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene'; import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine'; import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel'; +import { ShareTrailButton } from './ShareTrailButton'; import { getTrailStore } from './TrailStore/TrailStore'; import { ActionViewDefinition, @@ -33,7 +36,7 @@ import { VAR_GROUP_BY, VAR_METRIC_EXPR, } from './shared'; -import { getTrailFor } from './utils'; +import { getDataSource, getTrailFor } from './utils'; export interface MetricSceneState extends SceneObjectState { body: SceneFlexLayout; @@ -113,6 +116,32 @@ export class MetricActionBar extends SceneObjectBase { this.publishEvent(new OpenEmbeddedTrailEvent(), true); }; + public getLinkToExplore = async () => { + const metricScene = sceneGraph.getAncestor(this, MetricScene); + const trail = getTrailFor(this); + const dsValue = getDataSource(trail); + + const flexItem = metricScene.state.body.state.children[0] as SceneFlexItem; + const autoVizPanel = flexItem.state.body as AutoVizPanel; + const queries = autoVizPanel.state.queryDef?.queries || []; + const timeRange = sceneGraph.getTimeRange(autoVizPanel); + + return getExploreUrl({ + queries, + dsRef: { uid: dsValue }, + timeRange: timeRange.state.value, + scopedVars: { __sceneObject: { value: metricScene } }, + }); + }; + + public openExploreLink = async () => { + this.getLinkToExplore().then((link) => { + // We use window.open instead of a Link or because we want to compute the explore link when clicking, + // if we precompute it we have to keep track of a lot of dependencies + window.open(link, '_blank'); + }); + }; + public static Component = ({ model }: SceneComponentProps) => { const metricScene = sceneGraph.getAncestor(model, MetricScene); const styles = useStyles2(getStyles); @@ -129,11 +158,13 @@ export class MetricActionBar extends SceneObjectBase {
- - Explore - - Add to dashboard - + + { + const { origin } = useLocation(); + const [tooltip, setTooltip] = useState('Copy url'); + + const onShare = () => { + if (navigator.clipboard) { + navigator.clipboard.writeText(origin + getUrlForTrail(trail)); + setTooltip('Copied!'); + setTimeout(() => { + setTooltip('Copy url'); + }, 2000); + } + }; + + return ; +}; diff --git a/public/app/features/trails/utils.ts b/public/app/features/trails/utils.ts index 4ce16518f24..df5f62c0b77 100644 --- a/public/app/features/trails/utils.ts +++ b/public/app/features/trails/utils.ts @@ -1,5 +1,5 @@ import { urlUtil } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { config, getDataSourceSrv } from '@grafana/runtime'; import { getUrlSyncManager, sceneGraph, SceneObject, SceneObjectUrlValues, SceneTimeRange } from '@grafana/scenes'; import { getDatasourceSrv } from '../plugins/datasource_srv'; @@ -50,6 +50,14 @@ export function getMetricSceneFor(model: SceneObject): MetricScene { throw new Error('Unable to find trail'); } +export function getDataSource(trail: DataTrail) { + return sceneGraph.interpolate(trail, VAR_DATASOURCE_EXPR); +} + +export function getDataSourceName(dataSourceUid: string) { + return getDataSourceSrv().getInstanceSettings(dataSourceUid)?.name || dataSourceUid; +} + export function getDatasourceForNewTrail(): string | undefined { const prevTrail = getTrailStore().recent[0]; if (prevTrail) {