From dd5e3a08184b3d875c0ee62aab7ca9a054a8c23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Wed, 2 Nov 2022 11:22:09 +0100 Subject: [PATCH] Explore: Track data links usage (#56868) * Add tracking for data links in explore * Add tracking for data links in explore * Fix tests * Retrigger build --- public/app/features/explore/Explore.test.tsx | 1 + public/app/features/explore/Explore.tsx | 59 ++++++++++++++----- public/app/features/explore/ExploreGraph.tsx | 2 +- .../app/features/explore/ExploreToolbar.tsx | 2 +- public/app/features/explore/LogsContainer.tsx | 10 ++-- .../explore/NodeGraphContainer.test.tsx | 4 +- .../features/explore/NodeGraphContainer.tsx | 14 ++--- .../features/explore/TableContainer.test.tsx | 2 +- .../app/features/explore/TableContainer.tsx | 14 ++--- .../datasource/testdata/nodeGraphUtils.ts | 13 ++++ 10 files changed, 77 insertions(+), 44 deletions(-) diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index d7896d34129..aaaec6b7a93 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -62,6 +62,7 @@ const dummyProps: Props = { scanStopAction: scanStopAction, setQueries: jest.fn(), queryKeys: [], + queries: [], isLive: false, syncedTimes: false, updateTimeRange: jest.fn(), diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 8bbe7187d99..4863d04c0c0 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -1,4 +1,5 @@ import { css, cx } from '@emotion/css'; +import { get } from 'lodash'; import memoizeOne from 'memoize-one'; import React, { createRef } from 'react'; import { connect, ConnectedProps } from 'react-redux'; @@ -6,13 +7,22 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import { compose } from 'redux'; import { Unsubscribable } from 'rxjs'; -import { AbsoluteTimeRange, DataQuery, GrafanaTheme2, LoadingState, QueryFixAction, RawTimeRange } from '@grafana/data'; +import { + AbsoluteTimeRange, + DataQuery, + GrafanaTheme2, + LoadingState, + QueryFixAction, + RawTimeRange, + SplitOpenOptions, +} from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config, getDataSourceSrv } from '@grafana/runtime'; +import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; import { Collapse, CustomScrollbar, ErrorBoundaryAlert, Themeable2, withTheme2, PanelContainer } from '@grafana/ui'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types'; import appEvents from 'app/core/app_events'; import { supportedFeatures } from 'app/core/history/richHistoryStorageProvider'; +import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { getNodeGraphDataFrames } from 'app/plugins/panel/nodeGraph/utils'; import { StoreState } from 'app/types'; import { AbsoluteTimeEvent } from 'app/types/events'; @@ -228,6 +238,27 @@ export class Explore extends React.PureComponent { }); }; + onSplitOpen = (panelType: string) => { + return async (options?: SplitOpenOptions) => { + this.props.splitOpen(options); + if (options && this.props.datasourceInstance) { + const target = (await getDataSourceSrv().get(options.datasourceUid)).type; + const source = + this.props.datasourceInstance.uid === MIXED_DATASOURCE_NAME + ? get(this.props.queries, '0.datasource.type') + : this.props.datasourceInstance.type; + const tracking = { + origin: 'panel', + panelType, + source, + target, + exploreId: this.props.exploreId, + }; + reportInteraction('grafana_explore_split_view_opened', tracking); + } + }; + }; + renderEmptyState(exploreContainerStyles: string) { return (
@@ -241,17 +272,8 @@ export class Explore extends React.PureComponent { } renderGraphPanel(width: number) { - const { - graphResult, - absoluteRange, - timeZone, - splitOpen, - queryResponse, - loading, - theme, - graphStyle, - showFlameGraph, - } = this.props; + const { graphResult, absoluteRange, timeZone, queryResponse, loading, theme, graphStyle, showFlameGraph } = + this.props; const spacing = parseInt(theme.spacing(2).slice(0, -2), 10); const label = ; @@ -266,7 +288,7 @@ export class Explore extends React.PureComponent { onChangeTime={this.onUpdateTimeRange} timeZone={timeZone} annotations={queryResponse.annotations} - splitOpenFn={splitOpen} + splitOpenFn={this.onSplitOpen('graph')} loadingState={queryResponse.state} anchorToZero={false} /> @@ -283,6 +305,7 @@ export class Explore extends React.PureComponent { exploreId={exploreId} onCellFilterAdded={this.onCellFilterAdded} timeZone={timeZone} + splitOpenFn={this.onSplitOpen('table')} /> ); } @@ -301,6 +324,7 @@ export class Explore extends React.PureComponent { onStartScanning={this.onStartScanning} onStopScanning={this.onStopScanning} scrollElement={this.scrollElement} + splitOpenFn={this.onSplitOpen('logs')} /> ); } @@ -315,6 +339,7 @@ export class Explore extends React.PureComponent { exploreId={exploreId} withTraceView={showTrace} datasourceType={datasourceType} + splitOpenFn={this.onSplitOpen('nodeGraph')} /> ); } @@ -327,7 +352,7 @@ export class Explore extends React.PureComponent { } renderTraceViewPanel() { - const { queryResponse, splitOpen, exploreId } = this.props; + const { queryResponse, exploreId } = this.props; const dataFrames = queryResponse.series.filter((series) => series.meta?.preferredVisualisationType === 'trace'); return ( @@ -336,7 +361,7 @@ export class Explore extends React.PureComponent { void; tooltipDisplayMode?: TooltipDisplayMode; - splitOpenFn?: SplitOpen; + splitOpenFn: SplitOpen; onChangeTime: (timeRange: AbsoluteTimeRange) => void; graphStyle: ExploreGraphStyle; anchorToZero: boolean; diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index e2a0b67a0c7..6703018efd6 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -77,7 +77,7 @@ class UnConnectedExploreToolbar extends PureComponent { onOpenSplitView = () => { const { split } = this.props; split(); - reportInteraction('grafana_explore_split_view_opened'); + reportInteraction('grafana_explore_split_view_opened', { origin: 'menu' }); }; onCloseSplitView = () => { diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 30948289c5a..95ba04571d1 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -8,6 +8,7 @@ import { LoadingState, LogRowModel, RawTimeRange, + SplitOpen, } from '@grafana/data'; import { Collapse } from '@grafana/ui'; import { StoreState } from 'app/types'; @@ -17,7 +18,6 @@ import { getTimeZone } from '../profile/state/selectors'; import { LiveLogsWithTheme } from './LiveLogs'; import { Logs } from './Logs'; -import { splitOpen } from './state/main'; import { addResultsToCache, clearCache, loadLogsVolumeData, setLogsVolumeEnabled } from './state/query'; import { updateTimeRange } from './state/time'; import { LiveTailControls } from './useLiveTailControls'; @@ -35,6 +35,7 @@ interface LogsContainerProps extends PropsFromRedux { onClickFilterOutLabel?: (key: string, value: string) => void; onStartScanning: () => void; onStopScanning: () => void; + splitOpenFn: SplitOpen; } class LogsContainer extends PureComponent { @@ -69,7 +70,7 @@ class LogsContainer extends PureComponent { }; getFieldLinks = (field: Field, rowIndex: number) => { - const { splitOpen: splitOpenFn, range } = this.props; + const { splitOpenFn, range } = this.props; return getFieldLinksForExplore({ field, rowIndex, splitOpenFn, range }); }; @@ -93,7 +94,7 @@ class LogsContainer extends PureComponent { scanning, range, width, - splitOpen, + splitOpenFn, isLive, exploreId, addResultsToCache, @@ -135,7 +136,7 @@ class LogsContainer extends PureComponent { logsVolumeData={logsVolumeData} logsQueries={logsQueries} width={width} - splitOpen={splitOpen} + splitOpen={splitOpenFn} loading={loading} loadingState={loadingState} loadLogsVolumeData={loadLogsVolumeData} @@ -203,7 +204,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } const mapDispatchToProps = { updateTimeRange, - splitOpen, addResultsToCache, clearCache, loadLogsVolumeData, diff --git a/public/app/features/explore/NodeGraphContainer.test.tsx b/public/app/features/explore/NodeGraphContainer.test.tsx index e9bd95d2393..a624a2aeabd 100644 --- a/public/app/features/explore/NodeGraphContainer.test.tsx +++ b/public/app/features/explore/NodeGraphContainer.test.tsx @@ -14,7 +14,7 @@ describe('NodeGraphContainer', () => { dataFrames={[emptyFrame]} exploreId={ExploreId.left} range={getDefaultTimeRange()} - splitOpen={(() => {}) as any} + splitOpenFn={(() => {}) as any} withTraceView={true} datasourceType={''} /> @@ -30,7 +30,7 @@ describe('NodeGraphContainer', () => { dataFrames={[nodes]} exploreId={ExploreId.left} range={getDefaultTimeRange()} - splitOpen={(() => {}) as any} + splitOpenFn={(() => {}) as any} datasourceType={''} /> ); diff --git a/public/app/features/explore/NodeGraphContainer.tsx b/public/app/features/explore/NodeGraphContainer.tsx index 40e3f588942..0a0821bfd60 100644 --- a/public/app/features/explore/NodeGraphContainer.tsx +++ b/public/app/features/explore/NodeGraphContainer.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useToggle, useWindowSize } from 'react-use'; -import { applyFieldOverrides, DataFrame, GrafanaTheme2 } from '@grafana/data'; +import { applyFieldOverrides, DataFrame, GrafanaTheme2, SplitOpen } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Badge, Collapse, useStyles2, useTheme2 } from '@grafana/ui'; @@ -11,7 +11,6 @@ import { NodeGraph } from '../../plugins/panel/nodeGraph'; import { useCategorizeFrames } from '../../plugins/panel/nodeGraph/useCategorizeFrames'; import { ExploreId, StoreState } from '../../types'; -import { splitOpen } from './state/main'; import { useLinks } from './utils/links'; const getStyles = (theme: GrafanaTheme2) => ({ @@ -29,13 +28,14 @@ interface OwnProps { // When showing the node graph together with trace view we do some changes so it works better. withTraceView?: boolean; datasourceType: string; + splitOpenFn: SplitOpen; } type Props = OwnProps & ConnectedProps; export function UnconnectedNodeGraphContainer(props: Props) { - const { dataFrames, range, splitOpen, withTraceView, datasourceType } = props; - const getLinks = useLinks(range, splitOpen); + const { dataFrames, range, splitOpenFn, withTraceView, datasourceType } = props; + const getLinks = useLinks(range, splitOpenFn); const theme = useTheme2(); const styles = useStyles2(getStyles); @@ -116,9 +116,5 @@ function mapStateToProps(state: StoreState, { exploreId }: OwnProps) { }; } -const mapDispatchToProps = { - splitOpen, -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); +const connector = connect(mapStateToProps, {}); export const NodeGraphContainer = connector(UnconnectedNodeGraphContainer); diff --git a/public/app/features/explore/TableContainer.test.tsx b/public/app/features/explore/TableContainer.test.tsx index 5e4c8097c76..ad4bce55640 100644 --- a/public/app/features/explore/TableContainer.test.tsx +++ b/public/app/features/explore/TableContainer.test.tsx @@ -53,7 +53,7 @@ const defaultProps = { width: 800, onCellFilterAdded: jest.fn(), tableResult: dataFrame, - splitOpen: (() => {}) as any, + splitOpenFn: (() => {}) as any, range: {} as any, timeZone: InternalTimeZones.utc, }; diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index a31f1bdb2f9..40c986fea62 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { ValueLinkConfig, applyFieldOverrides, TimeZone } from '@grafana/data'; +import { ValueLinkConfig, applyFieldOverrides, TimeZone, SplitOpen } from '@grafana/data'; import { Collapse, Table } from '@grafana/ui'; import { FilterItem } from '@grafana/ui/src/components/Table/types'; import { config } from 'app/core/config'; @@ -10,7 +10,6 @@ import { StoreState } from 'app/types'; import { ExploreId, ExploreItemState } from 'app/types/explore'; import { MetaInfoText } from './MetaInfoText'; -import { splitOpen } from './state/main'; import { getFieldLinksForExplore } from './utils/links'; interface TableContainerProps { @@ -19,6 +18,7 @@ interface TableContainerProps { width: number; timeZone: TimeZone; onCellFilterAdded?: (filter: FilterItem) => void; + splitOpenFn: SplitOpen; } function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps) { @@ -30,11 +30,7 @@ function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps) return { loading, tableResult, range }; } -const mapDispatchToProps = { - splitOpen, -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); +const connector = connect(mapStateToProps, {}); type Props = TableContainerProps & ConnectedProps; @@ -51,7 +47,7 @@ export class TableContainer extends PureComponent { } render() { - const { loading, onCellFilterAdded, tableResult, width, splitOpen, range, ariaLabel, timeZone } = this.props; + const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, ariaLabel, timeZone } = this.props; const height = this.getTableHeight(); const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER; @@ -76,7 +72,7 @@ export class TableContainer extends PureComponent { return getFieldLinksForExplore({ field, rowIndex: config.valueRowIndex!, - splitOpenFn: splitOpen, + splitOpenFn, range, dataFrame: dataFrame!, }); diff --git a/public/app/plugins/datasource/testdata/nodeGraphUtils.ts b/public/app/plugins/datasource/testdata/nodeGraphUtils.ts index 325fd546446..894e5652401 100644 --- a/public/app/plugins/datasource/testdata/nodeGraphUtils.ts +++ b/public/app/plugins/datasource/testdata/nodeGraphUtils.ts @@ -55,6 +55,19 @@ export function generateRandomNodes(count = 10) { [NodeGraphDataFrameFieldNames.id]: { values: new ArrayVector(), type: FieldType.string, + config: { + links: [ + { + title: 'test data link', + url: '', + internal: { + query: { scenarioId: 'logs', alias: 'from service graph', stringInput: 'tes' }, + datasourceUid: 'gdev-testdata', + datasourceName: 'gdev-testdata', + }, + }, + ], + }, }, [NodeGraphDataFrameFieldNames.title]: { values: new ArrayVector(),