-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from quickwit-oss/feat_datalinks
Issue #34: datalinks / correlation
- Loading branch information
Showing
8 changed files
with
516 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <hr className={styles.dividerHideLine} />; | ||
} | ||
|
||
return <hr className={styles.divider} />; | ||
}; | ||
|
||
const getStyles = (theme: GrafanaTheme2) => ({ | ||
divider: css({ | ||
margin: theme.spacing(4, 0), | ||
}), | ||
dividerHideLine: css({ | ||
border: 'none', | ||
margin: theme.spacing(3, 0), | ||
}), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
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 [base64TraceId, setBase64TraceId] = useState(true) | ||
const labelWidth = 24 | ||
|
||
const handleChange = (field: keyof typeof value) => (event: React.ChangeEvent<HTMLInputElement>) => { | ||
onChange({ | ||
...value, | ||
[field]: event.currentTarget.value, | ||
}); | ||
}; | ||
|
||
const handleBase64TraceId = (base64TraceId: boolean, config: DataLinkConfig) => { | ||
setBase64TraceId(base64TraceId) | ||
config = {...config, base64TraceId: base64TraceId }; | ||
} | ||
|
||
return ( | ||
<div className={className}> | ||
<div className={styles.firstRow}> | ||
<InlineField | ||
label="Field" | ||
htmlFor="elasticsearch-datasource-config-field" | ||
labelWidth={labelWidth} | ||
tooltip={'Can be exact field name or a regex pattern that will match on the field name.'} | ||
> | ||
<Input | ||
type="text" | ||
id="elasticsearch-datasource-config-field" | ||
value={value.field} | ||
onChange={handleChange('field')} | ||
width={100} | ||
/> | ||
</InlineField> | ||
<Button | ||
variant={'destructive'} | ||
title="Remove field" | ||
icon="times" | ||
onClick={(event) => { | ||
event.preventDefault(); | ||
onDelete(); | ||
}} | ||
/> | ||
</div> | ||
|
||
<InlineFieldRow> | ||
<div className={styles.urlField}> | ||
<InlineLabel htmlFor="elasticsearch-datasource-internal-link" width={labelWidth}> | ||
{showInternalLink ? 'Query' : 'URL'} | ||
</InlineLabel> | ||
<DataLinkInput | ||
placeholder={showInternalLink ? '${__value.raw}' : 'http://example.com/${__value.raw}'} | ||
value={value.url || ''} | ||
onChange={(newValue) => | ||
onChange({ | ||
...value, | ||
url: newValue, | ||
}) | ||
} | ||
suggestions={suggestions} | ||
/> | ||
</div> | ||
|
||
<div className={styles.urlDisplayLabelField}> | ||
<InlineField | ||
label="URL Label" | ||
htmlFor="elasticsearch-datasource-url-label" | ||
labelWidth={14} | ||
tooltip={'Use to override the button label.'} | ||
> | ||
<Input | ||
type="text" | ||
id="elasticsearch-datasource-url-label" | ||
value={value.urlDisplayLabel} | ||
onChange={handleChange('urlDisplayLabel')} | ||
/> | ||
</InlineField> | ||
</div> | ||
</InlineFieldRow> | ||
|
||
<div className={styles.row}> | ||
<InlineField label="Field encoded in base64?" labelWidth={labelWidth} tooltip="Quickwit encodes the traceID in base64 by default whereas Jaeger uses hex"> | ||
<InlineSwitch | ||
value={base64TraceId} | ||
onChange={() => handleBase64TraceId(!base64TraceId, value)} | ||
/> | ||
</InlineField> | ||
</div> | ||
|
||
<div className={styles.row}> | ||
<InlineField label="Internal link" labelWidth={labelWidth}> | ||
<InlineSwitch | ||
label="Internal link" | ||
value={showInternalLink || false} | ||
onChange={() => { | ||
if (showInternalLink) { | ||
onChange({ | ||
...value, | ||
datasourceUid: undefined, | ||
}); | ||
} | ||
setShowInternalLink(!showInternalLink); | ||
}} | ||
/> | ||
</InlineField> | ||
|
||
{showInternalLink && ( | ||
<DataSourcePicker | ||
tracing={true} | ||
onChange={(ds: DataSourceInstanceSettings) => { | ||
onChange({ | ||
...value, | ||
datasourceUid: ds.uid, | ||
}); | ||
}} | ||
current={value.datasourceUid} | ||
/> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
function useInternalLink(datasourceUid?: string): [boolean, Dispatch<SetStateAction<boolean>>] { | ||
const [showInternalLink, setShowInternalLink] = useState<boolean>(!!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; | ||
`, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
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<Props>) => { | ||
const props: Props = { | ||
value: [], | ||
onChange: jest.fn(), | ||
...propOverrides, | ||
}; | ||
|
||
return render(<DataLinks {...props} />); | ||
}; | ||
|
||
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', | ||
base64TraceId: false, | ||
}, | ||
{ | ||
field: 'regex2', | ||
url: 'localhost2', | ||
base64TraceId: true, | ||
}, | ||
]; |
Oops, something went wrong.