Explore: Track data links usage (#56868)

* Add tracking for data links in explore

* Add tracking for data links in explore

* Fix tests

* Retrigger build
This commit is contained in:
Piotr Jamróz
2022-11-02 11:22:09 +01:00
committed by GitHub
parent c46a4a0b26
commit dd5e3a0818
10 changed files with 77 additions and 44 deletions

View File

@@ -62,6 +62,7 @@ const dummyProps: Props = {
scanStopAction: scanStopAction,
setQueries: jest.fn(),
queryKeys: [],
queries: [],
isLive: false,
syncedTimes: false,
updateTimeRange: jest.fn(),

View File

@@ -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<Props, ExploreState> {
});
};
onSplitOpen = (panelType: string) => {
return async (options?: SplitOpenOptions<DataQuery>) => {
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 (
<div className={cx(exploreContainerStyles)}>
@@ -241,17 +272,8 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
}
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 = <ExploreGraphLabel graphStyle={graphStyle} onChangeGraphStyle={this.onChangeGraphStyle} />;
@@ -266,7 +288,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
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<Props, ExploreState> {
exploreId={exploreId}
onCellFilterAdded={this.onCellFilterAdded}
timeZone={timeZone}
splitOpenFn={this.onSplitOpen('table')}
/>
);
}
@@ -301,6 +324,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
onStartScanning={this.onStartScanning}
onStopScanning={this.onStopScanning}
scrollElement={this.scrollElement}
splitOpenFn={this.onSplitOpen('logs')}
/>
);
}
@@ -315,6 +339,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
exploreId={exploreId}
withTraceView={showTrace}
datasourceType={datasourceType}
splitOpenFn={this.onSplitOpen('nodeGraph')}
/>
);
}
@@ -327,7 +352,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
}
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<Props, ExploreState> {
<TraceViewContainer
exploreId={exploreId}
dataFrames={dataFrames}
splitOpenFn={splitOpen}
splitOpenFn={this.onSplitOpen('traceView')}
scrollElement={this.scrollElement}
queryResponse={queryResponse}
topOfViewRef={this.topOfViewRef}
@@ -470,6 +495,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
datasourceInstance,
datasourceMissing,
queryKeys,
queries,
isLive,
graphResult,
logsResult,
@@ -489,6 +515,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
datasourceInstance,
datasourceMissing,
queryKeys,
queries,
isLive,
graphResult,
logsResult: logsResult ?? undefined,

View File

@@ -50,7 +50,7 @@ interface Props {
annotations?: DataFrame[];
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
tooltipDisplayMode?: TooltipDisplayMode;
splitOpenFn?: SplitOpen;
splitOpenFn: SplitOpen;
onChangeTime: (timeRange: AbsoluteTimeRange) => void;
graphStyle: ExploreGraphStyle;
anchorToZero: boolean;

View File

@@ -77,7 +77,7 @@ class UnConnectedExploreToolbar extends PureComponent<Props> {
onOpenSplitView = () => {
const { split } = this.props;
split();
reportInteraction('grafana_explore_split_view_opened');
reportInteraction('grafana_explore_split_view_opened', { origin: 'menu' });
};
onCloseSplitView = () => {

View File

@@ -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<LogsContainerProps> {
@@ -69,7 +70,7 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
};
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<LogsContainerProps> {
scanning,
range,
width,
splitOpen,
splitOpenFn,
isLive,
exploreId,
addResultsToCache,
@@ -135,7 +136,7 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
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,

View File

@@ -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={''}
/>
);

View File

@@ -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<typeof connector>;
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);

View File

@@ -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,
};

View File

@@ -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<typeof connector>;
@@ -51,7 +47,7 @@ export class TableContainer extends PureComponent<Props> {
}
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<Props> {
return getFieldLinksForExplore({
field,
rowIndex: config.valueRowIndex!,
splitOpenFn: splitOpen,
splitOpenFn,
range,
dataFrame: dataFrame!,
});

View File

@@ -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(),