diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index 3de2d2da2ca..35d87ca9e14 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -3,6 +3,7 @@ import React, { RefObject, useMemo, useState } from 'react'; import { useToggle } from 'react-use'; import { + CoreApp, DataFrame, DataLink, DataSourceApi, @@ -76,7 +77,7 @@ type Props = { }; export function TraceView(props: Props) { - const { spanFindMatches, traceProp, datasource, topOfViewRef, topOfViewRefType } = props; + const { spanFindMatches, traceProp, datasource, topOfViewRef, topOfViewRefType, exploreId } = props; const { detailStates, @@ -159,6 +160,7 @@ export function TraceView(props: Props) { <> { @@ -17,10 +22,12 @@ export const getStyles = () => { export type TracePageActionsProps = { traceId: string; + data: DataFrame; + app?: CoreApp; }; export default function TracePageActions(props: TracePageActionsProps) { - const { traceId } = props; + const { traceId, data, app } = props; const styles = useStyles2(getStyles); const [copyTraceIdClicked, setCopyTraceIdClicked] = useState(false); @@ -32,6 +39,16 @@ export default function TracePageActions(props: TracePageActionsProps) { }, 5000); }; + const exportTrace = () => { + const traceFormat = downloadTraceAsJson(data, 'Trace-' + traceId.substring(traceId.length - 6)); + reportInteraction('grafana_traces_download_traces_clicked', { + app, + grafana_version: config.buildInfo.version, + trace_format: traceFormat, + location: 'trace-view', + }); + }; + return (
- {/* alert('not implemented')} - ariaLabel={'Export Trace'} - label={'Export'} - icon={'save'} - /> */} +
); } diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx index 05b7ad0bee8..18b3c4fa408 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx @@ -15,6 +15,7 @@ import { getByText, render } from '@testing-library/react'; import React from 'react'; +import { MutableDataFrame } from '@grafana/data'; import config from 'app/core/config'; import { defaultFilters } from '../../useSearch'; @@ -36,6 +37,7 @@ const setup = () => { setFocusedSpanIdForSearch: jest.fn(), datasourceType: 'tempo', setHeaderHeight: jest.fn(), + data: new MutableDataFrame(), }; return render(); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx index 60c0e40a72d..e9f663851de 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx @@ -16,7 +16,7 @@ import { css } from '@emotion/css'; import cx from 'classnames'; import React, { memo, useEffect, useMemo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { CoreApp, DataFrame, GrafanaTheme2 } from '@grafana/data'; import { TimeZone } from '@grafana/schema'; import { Badge, BadgeColor, Tooltip, useStyles2 } from '@grafana/ui'; @@ -34,6 +34,8 @@ import { timestamp, getStyles } from './TracePageHeader'; export type TracePageHeaderProps = { trace: Trace | null; + data: DataFrame; + app?: CoreApp; timeZone: TimeZone; search: SearchProps; setSearch: React.Dispatch>; @@ -50,6 +52,8 @@ export type TracePageHeaderProps = { export const NewTracePageHeader = memo((props: TracePageHeaderProps) => { const { trace, + data, + app, timeZone, search, setSearch, @@ -101,7 +105,7 @@ export const NewTracePageHeader = memo((props: TracePageHeaderProps) => {
{links && links.length > 0 && } {title} - +
diff --git a/public/app/features/inspector/InspectDataTab.tsx b/public/app/features/inspector/InspectDataTab.tsx index d270b75511c..88e93c13984 100644 --- a/public/app/features/inspector/InspectDataTab.tsx +++ b/public/app/features/inspector/InspectDataTab.tsx @@ -9,7 +9,6 @@ import { CSVConfig, DataFrame, DataTransformerID, - MutableDataFrame, SelectableValue, TimeZone, transformDataFrame, @@ -22,13 +21,10 @@ import { t, Trans } from 'app/core/internationalization'; import { dataFrameToLogsModel } from 'app/core/logsModel'; import { PanelModel } from 'app/features/dashboard/state'; import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; -import { transformToJaeger } from 'app/plugins/datasource/jaeger/responseTransform'; -import { transformToOTLP } from 'app/plugins/datasource/tempo/resultTransformer'; -import { transformToZipkin } from 'app/plugins/datasource/zipkin/utils/transforms'; import { InspectDataOptions } from './InspectDataOptions'; import { getPanelInspectorStyles } from './styles'; -import { downloadAsJson, downloadDataFrameAsCsv, downloadLogsModelAsTxt } from './utils/download'; +import { downloadAsJson, downloadDataFrameAsCsv, downloadLogsModelAsTxt, downloadTraceAsJson } from './utils/download'; interface Props { isLoading: boolean; @@ -124,28 +120,7 @@ export class InspectDataTab extends PureComponent { if (df.meta?.preferredVisualisationType !== 'trace') { continue; } - let traceFormat = 'otlp'; - - switch (df.meta?.custom?.traceFormat) { - case 'jaeger': { - let res = transformToJaeger(new MutableDataFrame(df)); - downloadAsJson(res, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces'); - traceFormat = 'jaeger'; - break; - } - case 'zipkin': { - let res = transformToZipkin(new MutableDataFrame(df)); - downloadAsJson(res, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces'); - traceFormat = 'zipkin'; - break; - } - case 'otlp': - default: { - let res = transformToOTLP(new MutableDataFrame(df)); - downloadAsJson(res, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces'); - break; - } - } + const traceFormat = downloadTraceAsJson(df, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces'); reportInteraction('grafana_traces_download_traces_clicked', { app, diff --git a/public/app/features/inspector/utils/download.ts b/public/app/features/inspector/utils/download.ts index 5338ade3caa..23f76673b95 100644 --- a/public/app/features/inspector/utils/download.ts +++ b/public/app/features/inspector/utils/download.ts @@ -7,9 +7,14 @@ import { dateTimeFormat, dateTimeFormatISO, LogsModel, + MutableDataFrame, toCSV, } from '@grafana/data'; +import { transformToJaeger } from '../../../plugins/datasource/jaeger/responseTransform'; +import { transformToOTLP } from '../../../plugins/datasource/tempo/resultTransformer'; +import { transformToZipkin } from '../../../plugins/datasource/zipkin/utils/transforms'; + /** * Downloads a DataFrame as a TXT file. * @@ -77,3 +82,34 @@ export function downloadAsJson(json: unknown, title: string) { const fileName = `${title}-${dateTimeFormat(new Date())}.json`; saveAs(blob, fileName); } + +/** + * Downloads a trace as json, based on the DataFrame format or OTLP as a default + * + * @param {DataFrame} frame + * @param {string} title + */ +export function downloadTraceAsJson(frame: DataFrame, title: string): string { + let traceFormat = 'otlp'; + switch (frame.meta?.custom?.traceFormat) { + case 'jaeger': { + let res = transformToJaeger(new MutableDataFrame(frame)); + downloadAsJson(res, title); + traceFormat = 'jaeger'; + break; + } + case 'zipkin': { + let res = transformToZipkin(new MutableDataFrame(frame)); + downloadAsJson(res, title); + traceFormat = 'zipkin'; + break; + } + case 'otlp': + default: { + let res = transformToOTLP(new MutableDataFrame(frame)); + downloadAsJson(res, title); + break; + } + } + return traceFormat; +}