mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Trace View: Export trace button (#67368)
* Add export button to trace view * Support zipkin, jaeger and otlp formats
This commit is contained in:
parent
cd0fb21f29
commit
ffd83a027d
@ -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) {
|
||||
<>
|
||||
<NewTracePageHeader
|
||||
trace={traceProp}
|
||||
data={props.dataFrames[0]}
|
||||
timeZone={timeZone}
|
||||
search={newTraceViewHeaderSearch}
|
||||
setSearch={setNewTraceViewHeaderSearch}
|
||||
@ -170,6 +172,7 @@ export function TraceView(props: Props) {
|
||||
spanFilterMatches={spanFilterMatches}
|
||||
datasourceType={datasourceType}
|
||||
setHeaderHeight={setHeaderHeight}
|
||||
app={exploreId ? CoreApp.Explore : CoreApp.Unknown}
|
||||
/>
|
||||
<SpanGraph
|
||||
trace={traceProp}
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { CoreApp, DataFrame } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { config } from '../../../../../../core/config';
|
||||
import { downloadTraceAsJson } from '../../../../../inspector/utils/download';
|
||||
|
||||
import ActionButton from './ActionButton';
|
||||
|
||||
export const getStyles = () => {
|
||||
@ -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 (
|
||||
<div className={styles.TracePageActions}>
|
||||
<ActionButton
|
||||
@ -40,12 +57,7 @@ export default function TracePageActions(props: TracePageActionsProps) {
|
||||
label={copyTraceIdClicked ? 'Copied!' : 'Trace ID'}
|
||||
icon={'copy'}
|
||||
/>
|
||||
{/* <ActionButton
|
||||
onClick={() => alert('not implemented')}
|
||||
ariaLabel={'Export Trace'}
|
||||
label={'Export'}
|
||||
icon={'save'}
|
||||
/> */}
|
||||
<ActionButton onClick={exportTrace} ariaLabel={'Export Trace'} label={'Export'} icon={'save'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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(<NewTracePageHeader {...defaultProps} />);
|
||||
|
@ -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<React.SetStateAction<SearchProps>>;
|
||||
@ -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) => {
|
||||
<div className={styles.titleRow}>
|
||||
{links && links.length > 0 && <ExternalLinks links={links} className={styles.TracePageHeaderBack} />}
|
||||
{title}
|
||||
<TracePageActions traceId={trace.traceID} />
|
||||
<TracePageActions traceId={trace.traceID} data={data} app={app} />
|
||||
</div>
|
||||
|
||||
<div className={styles.subtitle}>
|
||||
|
@ -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<Props, State> {
|
||||
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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user