Tracing: Move upload trace to button (#69402)

* Tempo: Move upload trace to its own button

* Zipkin: Move upload trace to its own button

* Jaeger: Move upload trace to its own button

* Fix test

* Remove extra upload section from editor body

---------

Co-authored-by: Joey Tawadrous <joey.tawadrous@grafana.com>
This commit is contained in:
Andre Pereira
2023-06-23 12:03:00 +01:00
committed by GitHub
co-authored by Joey Tawadrous
parent 6180c77763
commit 08ed68e675
4 changed files with 167 additions and 88 deletions
@@ -1,8 +1,19 @@
import { css } from '@emotion/css';
import React from 'react';
import React, { useState } from 'react';
import { GrafanaTheme2, QueryEditorProps } from '@grafana/data';
import { FileDropzone, InlineField, InlineFieldRow, QueryField, RadioButtonGroup, useStyles2 } from '@grafana/ui';
import { QueryEditorProps } from '@grafana/data';
import {
Button,
FileDropzone,
HorizontalGroup,
InlineField,
InlineFieldRow,
Modal,
QueryField,
RadioButtonGroup,
useStyles2,
useTheme2,
} from '@grafana/ui';
import { JaegerDatasource } from '../datasource';
import { JaegerQuery, JaegerQueryType } from '../types';
@@ -12,6 +23,8 @@ import { SearchForm } from './SearchForm';
type Props = QueryEditorProps<JaegerDatasource, JaegerQuery>;
export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props) {
const [uploadModalOpen, setUploadModalOpen] = useState(false);
const theme = useTheme2();
const styles = useStyles2(getStyles);
const onChangeQuery = (value: string) => {
@@ -23,18 +36,6 @@ export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props)
switch (query.queryType) {
case 'search':
return <SearchForm datasource={datasource} query={query} onChange={onChange} />;
case 'upload':
return (
<div className={styles.fileDropzoneContainer}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
datasource.uploadedJson = result;
onRunQuery();
}}
/>
</div>
);
default:
return (
<InlineFieldRow>
@@ -54,24 +55,50 @@ export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props)
return (
<>
<Modal title={'Upload trace'} isOpen={uploadModalOpen} onDismiss={() => setUploadModalOpen(false)}>
<div className={css({ padding: theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
datasource.uploadedJson = result;
onChange({
...query,
queryType: 'upload',
});
setUploadModalOpen(false);
onRunQuery();
}}
/>
</div>
</Modal>
<div className={styles.container}>
<InlineFieldRow>
<InlineField label="Query type">
<RadioButtonGroup<JaegerQueryType>
options={[
{ value: 'search', label: 'Search' },
{ value: undefined, label: 'TraceID' },
{ value: 'upload', label: 'JSON File' },
]}
value={query.queryType}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
<InlineField label="Query type" grow={true}>
<HorizontalGroup spacing={'sm'} align={'center'} justify={'space-between'}>
<RadioButtonGroup<JaegerQueryType>
options={[
{ value: 'search', label: 'Search' },
{ value: undefined, label: 'TraceID' },
]}
value={query.queryType}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
<Button
variant="secondary"
size="sm"
onClick={() => {
setUploadModalOpen(true);
}}
>
Import trace
</Button>
</HorizontalGroup>
</InlineField>
</InlineFieldRow>
{renderEditorBody()}
@@ -80,11 +107,8 @@ export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props)
);
}
const getStyles = (theme: GrafanaTheme2) => ({
const getStyles = () => ({
container: css`
width: 100%;
`,
fileDropzoneContainer: css`
padding: ${theme.spacing(2)};
`,
});
@@ -5,10 +5,13 @@ import useAsync from 'react-use/lib/useAsync';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
import {
Button,
FileDropzone,
HorizontalGroup,
InlineField,
InlineFieldRow,
InlineLabel,
Modal,
RadioButtonGroup,
Themeable2,
withTheme2,
@@ -28,12 +31,18 @@ import { ServiceGraphSection } from './ServiceGraphSection';
import { getDS } from './utils';
interface Props extends QueryEditorProps<TempoDatasource, TempoQuery>, Themeable2 {}
interface State {
uploadModalOpen: boolean;
}
const DEFAULT_QUERY_TYPE: TempoQueryType = config.featureToggles.traceqlSearch ? 'traceqlSearch' : 'traceql';
class TempoQueryFieldComponent extends React.PureComponent<Props> {
class TempoQueryFieldComponent extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
uploadModalOpen: false,
};
}
// Set the default query type when the component mounts.
@@ -80,7 +89,6 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
let queryTypeOptions: Array<SelectableValue<TempoQueryType>> = [
{ value: 'traceql', label: 'TraceQL' },
{ value: 'upload', label: 'JSON File' },
{ value: 'serviceMap', label: 'Service Graph' },
];
@@ -104,29 +112,60 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
return (
<>
<InlineFieldRow>
<InlineField label="Query type">
<RadioButtonGroup<TempoQueryType>
options={queryTypeOptions}
value={query.queryType}
onChange={(v) => {
reportInteraction('grafana_traces_query_type_changed', {
datasourceType: 'tempo',
app: app ?? '',
grafana_version: config.buildInfo.version,
newQueryType: v,
previousQueryType: query.queryType ?? '',
});
this.onClearResults();
<Modal
title={'Upload trace'}
isOpen={this.state.uploadModalOpen}
onDismiss={() => this.setState({ uploadModalOpen: false })}
>
<div className={css({ padding: this.props.theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
this.props.datasource.uploadedJson = result;
onChange({
...query,
queryType: v,
queryType: 'upload',
});
this.setState({ uploadModalOpen: false });
this.props.onRunQuery();
}}
size="md"
/>
</div>
</Modal>
<InlineFieldRow>
<InlineField label="Query type" grow={true}>
<HorizontalGroup spacing={'sm'} align={'center'} justify={'space-between'}>
<RadioButtonGroup<TempoQueryType>
options={queryTypeOptions}
value={query.queryType}
onChange={(v) => {
reportInteraction('grafana_traces_query_type_changed', {
datasourceType: 'tempo',
app: app ?? '',
grafana_version: config.buildInfo.version,
newQueryType: v,
previousQueryType: query.queryType ?? '',
});
this.onClearResults();
onChange({
...query,
queryType: v,
});
}}
size="md"
/>
<Button
variant="secondary"
size="sm"
onClick={() => {
this.setState({ uploadModalOpen: true });
}}
>
Import trace
</Button>
</HorizontalGroup>
</InlineField>
</InlineFieldRow>
{query.queryType === 'search' && (
@@ -154,17 +193,6 @@ class TempoQueryFieldComponent extends React.PureComponent<Props> {
onBlur={this.props.onBlur}
/>
)}
{query.queryType === 'upload' && (
<div className={css({ padding: this.props.theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
this.props.datasource.uploadedJson = result;
this.props.onRunQuery();
}}
/>
</div>
)}
{query.queryType === 'serviceMap' && (
<ServiceGraphSection graphDatasourceUid={graphDatasourceUid} query={query} onChange={onChange} />
)}
@@ -15,7 +15,7 @@ describe('QueryField', () => {
<ZipkinQueryField
history={[]}
datasource={ds}
query={{ query: '1234' } as ZipkinQuery}
query={{ query: '1234', queryType: 'traceID' } as ZipkinQuery}
onRunQuery={() => {}}
onChange={() => {}}
/>
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { fromPairs } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useAsyncFn, useMount, useMountedState } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsyncFn';
@@ -15,6 +15,9 @@ import {
useTheme2,
QueryField,
useStyles2,
Modal,
HorizontalGroup,
Button,
} from '@grafana/ui';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
@@ -36,6 +39,7 @@ const getStyles = (theme: GrafanaTheme2) => {
};
export const ZipkinQueryField = ({ query, onChange, onRunQuery, datasource }: Props) => {
const [uploadModalOpen, setUploadModalOpen] = useState(false);
const serviceOptions = useServices(datasource);
const theme = useTheme2();
const styles = useStyles2(getStyles);
@@ -52,6 +56,15 @@ export const ZipkinQueryField = ({ query, onChange, onRunQuery, datasource }: Pr
[onChange, onRunQuery, query]
);
useEffect(() => {
if (!query.queryType) {
onChange({
...query,
queryType: 'traceID',
});
}
}, [query, onChange]);
const onChangeQuery = (value: string) => {
const nextQuery = { ...query, query: value };
onChange(nextQuery);
@@ -61,35 +74,49 @@ export const ZipkinQueryField = ({ query, onChange, onRunQuery, datasource }: Pr
return (
<>
<InlineFieldRow>
<InlineField label="Query type">
<RadioButtonGroup<ZipkinQueryType>
options={[
{ value: 'traceID', label: 'TraceID' },
{ value: 'upload', label: 'JSON File' },
]}
value={query.queryType || 'traceID'}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
</InlineField>
</InlineFieldRow>
{query.queryType === 'upload' ? (
<Modal title={'Upload trace'} isOpen={uploadModalOpen} onDismiss={() => setUploadModalOpen(false)}>
<div className={css({ padding: theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
datasource.uploadedJson = result;
onChange({
...query,
queryType: 'upload',
});
setUploadModalOpen(false);
onRunQuery();
}}
/>
</div>
) : (
</Modal>
<InlineFieldRow>
<InlineField label="Query type" grow={true}>
<HorizontalGroup spacing={'sm'} align={'center'} justify={'space-between'}>
<RadioButtonGroup<ZipkinQueryType>
options={[{ value: 'traceID', label: 'TraceID' }]}
value={query.queryType || 'traceID'}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
<Button
variant="secondary"
size="sm"
onClick={() => {
setUploadModalOpen(true);
}}
>
Import trace
</Button>
</HorizontalGroup>
</InlineField>
</InlineFieldRow>
{query.queryType === 'traceID' && (
<InlineFieldRow>
<ButtonCascader
options={cascaderOptions}