From b70d8837c0ec2aca46cc0c3dd2a1ba12f5bb607b Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Fri, 8 Dec 2023 08:37:13 +0100 Subject: [PATCH 1/9] Issue #34: add jaeger to docker-compose --- docker-compose.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 75a87ab..e642a74 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,16 @@ version: '3.0' services: + jaeger: + image: jaegertracing/jaeger-query:1.51 + container_name: 'jaeger-quickwit' + environment: + - GRPC_STORAGE_SERVER=host.docker.internal:7280 + - SPAN_STORAGE_TYPE=grpc-plugin + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - quickwit grafana: container_name: 'grafana-quickwit-datasource' build: @@ -15,6 +25,12 @@ services: - gquickwit:/var/lib/grafana extra_hosts: - "host.docker.internal:host-gateway" + networks: + - quickwit + +networks: + quickwit: + driver: bridge volumes: gquickwit: From d9305e2e5181b19cd9dc6e70e64e9b0e930c9eef Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Fri, 8 Dec 2023 09:48:53 +0100 Subject: [PATCH 2/9] Issue #34: add DataLinks and DataLink webcomponent --- src/components/Divider.tsx | 25 ++++ src/configuration/ConfigEditor.tsx | 24 ++++ src/configuration/DataLink.tsx | 176 +++++++++++++++++++++++++++ src/configuration/DataLinks.test.tsx | 67 ++++++++++ src/configuration/DataLinks.tsx | 88 ++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 src/components/Divider.tsx create mode 100644 src/configuration/DataLink.tsx create mode 100644 src/configuration/DataLinks.test.tsx create mode 100644 src/configuration/DataLinks.tsx diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx new file mode 100644 index 0000000..11f0239 --- /dev/null +++ b/src/components/Divider.tsx @@ -0,0 +1,25 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +export const Divider = ({ hideLine = false }) => { + const styles = useStyles2(getStyles); + + if (hideLine) { + return
; + } + + return
; +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + divider: css({ + margin: theme.spacing(4, 0), + }), + dividerHideLine: css({ + border: 'none', + margin: theme.spacing(3, 0), + }), +}); diff --git a/src/configuration/ConfigEditor.tsx b/src/configuration/ConfigEditor.tsx index efa011c..18e7dba 100644 --- a/src/configuration/ConfigEditor.tsx +++ b/src/configuration/ConfigEditor.tsx @@ -3,6 +3,8 @@ import { DataSourceHttpSettings, Input, InlineField, FieldSet } from '@grafana/u import { DataSourcePluginOptionsEditorProps, DataSourceSettings } from '@grafana/data'; import { QuickwitOptions } from 'quickwit'; import { coerceOptions } from './utils'; +import { Divider } from 'components/Divider'; +import { DataLinks } from './DataLinks'; interface Props extends DataSourcePluginOptionsEditorProps {} @@ -27,6 +29,7 @@ export const ConfigEditor = (props: Props) => { onChange={onOptionsChange} /> + ); }; @@ -35,6 +38,27 @@ type DetailsProps = { value: DataSourceSettings; onChange: (value: DataSourceSettings) => void; }; + +export const QuickwitDataLinks = ({ value, onChange }: DetailsProps) => { + return ( +
+ + { + onChange({ + ...value, + jsonData: { + ...value.jsonData, + dataLinks: newValue, + }, + }); + }} + /> +
+ ) +}; + export const QuickwitDetails = ({ value, onChange }: DetailsProps) => { return ( <> diff --git a/src/configuration/DataLink.tsx b/src/configuration/DataLink.tsx new file mode 100644 index 0000000..24eeccc --- /dev/null +++ b/src/configuration/DataLink.tsx @@ -0,0 +1,176 @@ +import { css } from '@emotion/css'; +import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; + +import { DataSourceInstanceSettings, VariableSuggestion } from '@grafana/data'; +import { + Button, + DataLinkInput, + InlineField, + InlineSwitch, + InlineFieldRow, + InlineLabel, + Input, + useStyles2 +} from '@grafana/ui'; + +import { DataSourcePicker } from '@grafana/runtime' + +import { DataLinkConfig } from '../types'; + +interface Props { + value: DataLinkConfig; + onChange: (value: DataLinkConfig) => void; + onDelete: () => void; + suggestions: VariableSuggestion[]; + className?: string; +} + +export const DataLink = (props: Props) => { + const { value, onChange, onDelete, suggestions, className } = props; + const styles = useStyles2(getStyles); + const [showInternalLink, setShowInternalLink] = useInternalLink(value.datasourceUid); + + const handleChange = (field: keyof typeof value) => (event: React.ChangeEvent) => { + onChange({ + ...value, + [field]: event.currentTarget.value, + }); + }; + + return ( +
+
+ + + +
+ + +
+ + {showInternalLink ? 'Query' : 'URL'} + + + onChange({ + ...value, + url: newValue, + }) + } + suggestions={suggestions} + /> +
+ +
+ + + +
+
+ +
+ + { + if (showInternalLink) { + onChange({ + ...value, + datasourceUid: undefined, + }); + } + setShowInternalLink(!showInternalLink); + }} + /> + + + {showInternalLink && ( + { + onChange({ + ...value, + datasourceUid: ds.uid, + }); + }} + current={value.datasourceUid} + /> + )} +
+
+ ); +}; + +function useInternalLink(datasourceUid?: string): [boolean, Dispatch>] { + const [showInternalLink, setShowInternalLink] = useState(!!datasourceUid); + const previousUid = usePrevious(datasourceUid); + + // Force internal link visibility change if uid changed outside of this component. + useEffect(() => { + if (!previousUid && datasourceUid && !showInternalLink) { + setShowInternalLink(true); + } + if (previousUid && !datasourceUid && showInternalLink) { + setShowInternalLink(false); + } + }, [previousUid, datasourceUid, showInternalLink]); + + return [showInternalLink, setShowInternalLink]; +} + +const getStyles = () => ({ + firstRow: css` + display: flex; + `, + nameField: css` + flex: 2; + `, + regexField: css` + flex: 3; + `, + row: css` + display: flex; + align-items: baseline; + `, + urlField: css` + display: flex; + flex: 1; + `, + urlDisplayLabelField: css` + flex: 1; + `, +}); diff --git a/src/configuration/DataLinks.test.tsx b/src/configuration/DataLinks.test.tsx new file mode 100644 index 0000000..a623d68 --- /dev/null +++ b/src/configuration/DataLinks.test.tsx @@ -0,0 +1,67 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { DataLinkConfig } from '../types'; + +import { DataLinks, Props } from './DataLinks'; + +const setup = (propOverrides?: Partial) => { + const props: Props = { + value: [], + onChange: jest.fn(), + ...propOverrides, + }; + + return render(); +}; + +describe('DataLinks tests', () => { + it('should render correctly with no fields', async () => { + setup(); + + expect(screen.getByRole('heading', { name: 'Data links' })); + expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument(); + expect(await screen.findAllByRole('button')).toHaveLength(1); + }); + + it('should render correctly when passed fields', async () => { + setup({ value: testValue }); + + expect(await screen.findAllByRole('button', { name: 'Remove field' })).toHaveLength(2); + expect(await screen.findAllByRole('checkbox', { name: 'Internal link' })).toHaveLength(2); + }); + + it('should call onChange to add a new field when the add button is clicked', async () => { + const onChangeMock = jest.fn(); + setup({ onChange: onChangeMock }); + + expect(onChangeMock).not.toHaveBeenCalled(); + const addButton = screen.getByRole('button', { name: 'Add' }); + await userEvent.click(addButton); + + expect(onChangeMock).toHaveBeenCalled(); + }); + + it('should call onChange to remove a field when the remove button is clicked', async () => { + const onChangeMock = jest.fn(); + setup({ value: testValue, onChange: onChangeMock }); + + expect(onChangeMock).not.toHaveBeenCalled(); + const removeButton = await screen.findAllByRole('button', { name: 'Remove field' }); + await userEvent.click(removeButton[0]); + + expect(onChangeMock).toHaveBeenCalled(); + }); +}); + +const testValue: DataLinkConfig[] = [ + { + field: 'regex1', + url: 'localhost1', + }, + { + field: 'regex2', + url: 'localhost2', + }, +]; diff --git a/src/configuration/DataLinks.tsx b/src/configuration/DataLinks.tsx new file mode 100644 index 0000000..2ece07a --- /dev/null +++ b/src/configuration/DataLinks.tsx @@ -0,0 +1,88 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data'; +import { ConfigSubSection } from '@grafana/experimental'; +import { Button, useStyles2 } from '@grafana/ui'; + +import { DataLinkConfig } from '../types'; + +import { DataLink } from './DataLink'; + +const getStyles = (theme: GrafanaTheme2) => { + return { + addButton: css` + margin-right: 10px; + `, + container: css` + margin-bottom: ${theme.spacing(2)}; + `, + dataLink: css` + margin-bottom: ${theme.spacing(1)}; + `, + }; +}; + +export type Props = { + value?: DataLinkConfig[]; + onChange: (value: DataLinkConfig[]) => void; +}; +export const DataLinks = (props: Props) => { + const { value, onChange } = props; + const styles = useStyles2(getStyles); + + return ( + +
+ {value && value.length > 0 && ( +
+ {value.map((field, index) => { + return ( + { + const newDataLinks = [...value]; + newDataLinks.splice(index, 1, newField); + onChange(newDataLinks); + }} + onDelete={() => { + const newDataLinks = [...value]; + newDataLinks.splice(index, 1); + onChange(newDataLinks); + }} + suggestions={[ + { + value: DataLinkBuiltInVars.valueRaw, + label: 'Raw value', + documentation: 'Raw value of the field', + origin: VariableOrigin.Value, + }, + ]} + /> + ); + })} +
+ )} + + +
+
+ ); +}; From 31f68a95f0ab51ef552b179aa99a89e400cc17b4 Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Fri, 8 Dec 2023 10:37:46 +0100 Subject: [PATCH 3/9] Issue #34: fix docker-compose config for jaegger --- docker-compose.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index e642a74..7a84d0a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,8 +5,10 @@ services: image: jaegertracing/jaeger-query:1.51 container_name: 'jaeger-quickwit' environment: - - GRPC_STORAGE_SERVER=host.docker.internal:7280 + - GRPC_STORAGE_SERVER=host.docker.internal:7281 - SPAN_STORAGE_TYPE=grpc-plugin + ports: + - 16686:16686 extra_hosts: - "host.docker.internal:host-gateway" networks: From d387f4cf2d2e7d68c619fdaf836a014aa80bfe8d Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Fri, 8 Dec 2023 15:25:31 +0100 Subject: [PATCH 4/9] Issue #34: add enhanceDataFrameWithDataLinks --- src/datasource.ts | 66 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/datasource.ts b/src/datasource.ts index 36cd0a2..6d9ade0 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -6,6 +6,7 @@ import { AbstractQuery, CoreApp, DataFrame, + DataLink, DataQueryError, DataQueryRequest, DataQueryResponse, @@ -32,7 +33,11 @@ import { TimeRange, } from '@grafana/data'; import { BucketAggregation, DataLinkConfig, ElasticsearchQuery, Field, FieldMapping, IndexMetadata, Logs, TermsQuery } from './types'; -import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import { + DataSourceWithBackend, + getTemplateSrv, + TemplateSrv, + getDataSourceSrv } from '@grafana/runtime'; import { LogRowContextOptions, LogRowContextQueryDirection, QuickwitOptions } from 'quickwit'; import { ElasticQueryBuilder } from 'QueryBuilder'; import { colors } from '@grafana/ui'; @@ -83,15 +88,15 @@ export class QuickwitDataSource this.languageProvider = new ElasticsearchLanguageProvider(this); } - // /** - // * Ideally final -- any other implementation may not work as expected - // */ - // query(request: DataQueryRequest): Observable { - // return super.query(request) - // .pipe(map((response) => { - // return response; - // })); - // } + query(request: DataQueryRequest): Observable { + return super.query(request) + .pipe(map((response) => { + response.data.forEach((dataFrame) => { + enhanceDataFrameWithDataLinks(dataFrame, this.dataLinks); + }); + return response; + })); + } /** * Checks the plugin health @@ -816,6 +821,46 @@ function luceneEscape(value: string) { return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1'); } +export function enhanceDataFrameWithDataLinks(dataFrame: DataFrame, dataLinks: DataLinkConfig[]) { + if (!dataLinks.length) { + return; + } + + for (const field of dataFrame.fields) { + const linksToApply = dataLinks.filter((dataLink) => new RegExp(dataLink.field).test(field.name)); + + if (linksToApply.length === 0) { + continue; + } + + field.config = field.config || {}; + field.config.links = [...(field.config.links || [], linksToApply.map(generateDataLink))]; + } +} + +function generateDataLink(linkConfig: DataLinkConfig): DataLink { + const dataSourceSrv = getDataSourceSrv(); + + if (linkConfig.datasourceUid) { + const dsSettings = dataSourceSrv.getInstanceSettings(linkConfig.datasourceUid); + + return { + title: linkConfig.urlDisplayLabel || '', + url: '', + internal: { + query: { query: linkConfig.url }, + datasourceUid: linkConfig.datasourceUid, + datasourceName: dsSettings?.name ?? 'Data source not found', + }, + }; + } else { + return { + title: linkConfig.urlDisplayLabel || '', + url: linkConfig.url, + }; + } +} + function createContextTimeRange(rowTimeEpochMs: number, direction: string) { const offset = 7; // For log context, we want to request data from 7 subsequent/previous indices @@ -831,4 +876,3 @@ function createContextTimeRange(rowTimeEpochMs: number, direction: string) { }; } } - From ca0e60aff8eb9ba4daba794e91de418fd37aaa31 Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Mon, 11 Dec 2023 11:30:09 +0100 Subject: [PATCH 5/9] Issue #34: replace regexp by equal comparison --- src/datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasource.ts b/src/datasource.ts index 6d9ade0..c47d444 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -827,7 +827,7 @@ export function enhanceDataFrameWithDataLinks(dataFrame: DataFrame, dataLinks: D } for (const field of dataFrame.fields) { - const linksToApply = dataLinks.filter((dataLink) => new RegExp(dataLink.field).test(field.name)); + const linksToApply = dataLinks.filter((dataLink) => dataLink.field === field.name); if (linksToApply.length === 0) { continue; From 242324ffe8a985223c669c5d5b285612a7389381 Mon Sep 17 00:00:00 2001 From: fmassot Date: Tue, 12 Dec 2023 20:50:09 +0100 Subject: [PATCH 6/9] Add parameter to convert a traceId from base64 to hex string --- src/configuration/DataLink.tsx | 2 +- src/configuration/DataLinks.test.tsx | 2 ++ src/configuration/DataLinks.tsx | 2 +- src/datasource.ts | 48 +++++++++++++++++++++++++--- src/types.ts | 1 + 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/configuration/DataLink.tsx b/src/configuration/DataLink.tsx index 24eeccc..c2cb76c 100644 --- a/src/configuration/DataLink.tsx +++ b/src/configuration/DataLink.tsx @@ -11,7 +11,7 @@ import { InlineFieldRow, InlineLabel, Input, - useStyles2 + useStyles2, } from '@grafana/ui'; import { DataSourcePicker } from '@grafana/runtime' diff --git a/src/configuration/DataLinks.test.tsx b/src/configuration/DataLinks.test.tsx index a623d68..17cfb1d 100644 --- a/src/configuration/DataLinks.test.tsx +++ b/src/configuration/DataLinks.test.tsx @@ -59,9 +59,11 @@ const testValue: DataLinkConfig[] = [ { field: 'regex1', url: 'localhost1', + base64TraceId: false, }, { field: 'regex2', url: 'localhost2', + base64TraceId: true, }, ]; diff --git a/src/configuration/DataLinks.tsx b/src/configuration/DataLinks.tsx index 2ece07a..9f066cc 100644 --- a/src/configuration/DataLinks.tsx +++ b/src/configuration/DataLinks.tsx @@ -76,7 +76,7 @@ export const DataLinks = (props: Props) => { icon="plus" onClick={(event) => { event.preventDefault(); - const newDataLinks = [...(value || []), { field: '', url: '' }]; + const newDataLinks = [...(value || []), { field: '', url: '', base64TraceId: true }]; onChange(newDataLinks); }} > diff --git a/src/datasource.ts b/src/datasource.ts index c47d444..acba41e 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -4,6 +4,7 @@ import { catchError, mergeMap, map } from 'rxjs/operators'; import { AbstractQuery, + ArrayVector, CoreApp, DataFrame, DataLink, @@ -17,6 +18,7 @@ import { DataSourceWithQueryImportSupport, DataSourceWithSupplementaryQueriesSupport, dateTime, + Field, FieldColorModeId, FieldType, getDefaultTimeRange, @@ -32,7 +34,7 @@ import { SupplementaryQueryType, TimeRange, } from '@grafana/data'; -import { BucketAggregation, DataLinkConfig, ElasticsearchQuery, Field, FieldMapping, IndexMetadata, Logs, TermsQuery } from './types'; +import { BucketAggregation, DataLinkConfig, ElasticsearchQuery, Field as QuickwitField, FieldMapping, IndexMetadata, Logs, TermsQuery } from './types'; import { DataSourceWithBackend, getTemplateSrv, @@ -348,7 +350,7 @@ export class QuickwitDataSource }; return from(this.getResource('indexes/' + this.index)).pipe( map((index_metadata) => { - const shouldAddField = (field: Field) => { + const shouldAddField = (field: QuickwitField) => { const translated_type = typeMap[field.field_mapping.type]; if (type?.length === 0) { return true; @@ -578,8 +580,8 @@ export class QuickwitDataSource } // Returns a flatten array of fields and nested fields found in the given `FieldMapping` array. -function getAllFields(field_mappings: FieldMapping[]): Field[] { - const fields: Field[] = []; +function getAllFields(field_mappings: FieldMapping[]): QuickwitField[] { + const fields: QuickwitField[] = []; for (const field_mapping of field_mappings) { if (field_mapping.type === 'object' && field_mapping.field_mappings !== undefined) { for (const child_field_mapping of getAllFields(field_mapping.field_mappings)) { @@ -821,13 +823,51 @@ function luceneEscape(value: string) { return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1'); } +function base64ToHex(base64String: string) { + const binaryString = window.atob(base64String); + return Array.from(binaryString).map(char => { + const byte = char.charCodeAt(0); + return ('0' + byte.toString(16)).slice(-2); + }).join(''); +} + export function enhanceDataFrameWithDataLinks(dataFrame: DataFrame, dataLinks: DataLinkConfig[]) { if (!dataLinks.length) { return; } + let fields_to_fix_condition = (field: Field) => { + return dataLinks.filter((dataLink) => dataLink.field === field.name && dataLink.base64TraceId).length === 1; + }; + const fields_to_keep = dataFrame.fields.filter((field) => { + return !fields_to_fix_condition(field) + }); + let new_fields = dataFrame + .fields + .filter(fields_to_fix_condition) + .map((field) => { + let values = field.values.toArray().map((value) => { + try { + return base64ToHex(value); + } catch (e) { + console.warn("cannot convert value from base64 to hex", e); + return value; + }; + }); + return { + ...field, + values: new ArrayVector(values), + } + }); + + if (new_fields.length === 0) { + return; + } + + dataFrame.fields = [new_fields[0], ...fields_to_keep]; for (const field of dataFrame.fields) { const linksToApply = dataLinks.filter((dataLink) => dataLink.field === field.name); + console.log(linksToApply); if (linksToApply.length === 0) { continue; diff --git a/src/types.ts b/src/types.ts index 9ef2672..f2a9d9c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,6 +100,7 @@ export interface TermsQuery { export type DataLinkConfig = { field: string; + base64TraceId: boolean; url: string; urlDisplayLabel?: string; datasourceUid?: string; From d76a42d7c84ccf8005b0f12bc79270a1e76e3609 Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Wed, 13 Dec 2023 09:57:47 +0100 Subject: [PATCH 7/9] Issue #34: add switch to enable/disable base64 traceId encoding --- src/configuration/DataLink.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/configuration/DataLink.tsx b/src/configuration/DataLink.tsx index c2cb76c..bfde386 100644 --- a/src/configuration/DataLink.tsx +++ b/src/configuration/DataLink.tsx @@ -30,6 +30,7 @@ export const DataLink = (props: Props) => { const { value, onChange, onDelete, suggestions, className } = props; const styles = useStyles2(getStyles); const [showInternalLink, setShowInternalLink] = useInternalLink(value.datasourceUid); + const [base64TraceId, setBase64TraceId] = useState(true) const handleChange = (field: keyof typeof value) => (event: React.ChangeEvent) => { onChange({ @@ -38,6 +39,11 @@ export const DataLink = (props: Props) => { }); }; + const handleBase64TraceId = (base64TraceId: boolean, config: DataLinkConfig) => { + setBase64TraceId(base64TraceId) + config = {...config, base64TraceId: base64TraceId }; + } + return (
@@ -131,6 +137,17 @@ export const DataLink = (props: Props) => { /> )}
+ +
+ + handleBase64TraceId(!base64TraceId, value)} + /> + +
); }; From 71578d4122fdce943a786985b12677dce0d0ffbf Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Wed, 13 Dec 2023 10:00:07 +0100 Subject: [PATCH 8/9] Issue #34: move title tooltip --- src/configuration/DataLink.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/configuration/DataLink.tsx b/src/configuration/DataLink.tsx index bfde386..1cc4565 100644 --- a/src/configuration/DataLink.tsx +++ b/src/configuration/DataLink.tsx @@ -139,9 +139,8 @@ export const DataLink = (props: Props) => {
- + handleBase64TraceId(!base64TraceId, value)} From 65ff78acb7f4ec1239a989b3fac371f12f7d632e Mon Sep 17 00:00:00 2001 From: Idriss Neumann Date: Wed, 13 Dec 2023 11:54:00 +0100 Subject: [PATCH 9/9] Issue #34: design review --- src/configuration/DataLink.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/configuration/DataLink.tsx b/src/configuration/DataLink.tsx index 1cc4565..f5a28b1 100644 --- a/src/configuration/DataLink.tsx +++ b/src/configuration/DataLink.tsx @@ -31,6 +31,7 @@ export const DataLink = (props: Props) => { const styles = useStyles2(getStyles); const [showInternalLink, setShowInternalLink] = useInternalLink(value.datasourceUid); const [base64TraceId, setBase64TraceId] = useState(true) + const labelWidth = 24 const handleChange = (field: keyof typeof value) => (event: React.ChangeEvent) => { onChange({ @@ -50,7 +51,7 @@ export const DataLink = (props: Props) => { {
- + {showInternalLink ? 'Query' : 'URL'} {
- + + handleBase64TraceId(!base64TraceId, value)} + /> + +
+ +
+ { /> )}
- -
- - handleBase64TraceId(!base64TraceId, value)} - /> - -
); };