mirror of https://github.com/grafana/grafana
QueryVariable: Be able to edit the variable using scenes (#80847)
parent
0851c18b55
commit
a9f17a3f24
@ -0,0 +1,78 @@ |
||||
import React from 'react'; |
||||
|
||||
import { DataSourceApi, LoadingState, TimeRange } from '@grafana/data'; |
||||
import { getTemplateSrv } from '@grafana/runtime'; |
||||
import { QueryVariable } from '@grafana/scenes'; |
||||
import { Text, Box } from '@grafana/ui'; |
||||
import { isLegacyQueryEditor, isQueryEditor } from 'app/features/variables/guard'; |
||||
import { VariableQueryEditorType } from 'app/features/variables/types'; |
||||
|
||||
type VariableQueryType = QueryVariable['state']['query']; |
||||
|
||||
interface QueryEditorProps { |
||||
query: VariableQueryType; |
||||
datasource: DataSourceApi; |
||||
VariableQueryEditor: VariableQueryEditorType; |
||||
timeRange: TimeRange; |
||||
onLegacyQueryChange: (query: VariableQueryType, definition: string) => void; |
||||
onQueryChange: (query: VariableQueryType) => void; |
||||
} |
||||
|
||||
export function QueryEditor({ |
||||
query, |
||||
datasource, |
||||
VariableQueryEditor, |
||||
onLegacyQueryChange, |
||||
onQueryChange, |
||||
timeRange, |
||||
}: QueryEditorProps) { |
||||
let queryWithDefaults; |
||||
if (typeof query === 'string') { |
||||
queryWithDefaults = query || (datasource.variables?.getDefaultQuery?.() ?? ''); |
||||
} else { |
||||
queryWithDefaults = { |
||||
...datasource.variables?.getDefaultQuery?.(), |
||||
...query, |
||||
}; |
||||
} |
||||
|
||||
if (VariableQueryEditor && isLegacyQueryEditor(VariableQueryEditor, datasource)) { |
||||
return ( |
||||
<Box marginBottom={2}> |
||||
<Text element={'h4'}>Query</Text> |
||||
<Box marginTop={1}> |
||||
<VariableQueryEditor |
||||
key={datasource.uid} |
||||
datasource={datasource} |
||||
query={queryWithDefaults} |
||||
templateSrv={getTemplateSrv()} |
||||
onChange={onLegacyQueryChange} |
||||
/> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
} |
||||
|
||||
if (VariableQueryEditor && isQueryEditor(VariableQueryEditor, datasource)) { |
||||
return ( |
||||
<Box marginBottom={2}> |
||||
<Text element={'h4'}>Query</Text> |
||||
<Box marginTop={1}> |
||||
<VariableQueryEditor |
||||
key={datasource.uid} |
||||
datasource={datasource} |
||||
query={queryWithDefaults} |
||||
onChange={onQueryChange} |
||||
onRunQuery={() => {}} |
||||
data={{ series: [], state: LoadingState.Done, timeRange }} |
||||
range={timeRange} |
||||
onBlur={() => {}} |
||||
history={[]} |
||||
/> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
@ -0,0 +1,269 @@ |
||||
import { render, screen, waitFor } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React, { FormEvent } from 'react'; |
||||
import { of } from 'rxjs'; |
||||
import { MockDataSourceApi } from 'test/mocks/datasource_srv'; |
||||
|
||||
import { |
||||
LoadingState, |
||||
PanelData, |
||||
getDefaultTimeRange, |
||||
toDataFrame, |
||||
FieldType, |
||||
VariableSupportType, |
||||
} from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { setRunRequest } from '@grafana/runtime'; |
||||
import { VariableRefresh, VariableSort } from '@grafana/schema'; |
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks'; |
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor'; |
||||
|
||||
import { QueryVariableEditorForm } from './QueryVariableForm'; |
||||
|
||||
const defaultDatasource = mockDataSource({ |
||||
name: 'Default Test Data Source', |
||||
type: 'test', |
||||
}); |
||||
|
||||
const promDatasource = mockDataSource({ |
||||
name: 'Prometheus', |
||||
type: 'prometheus', |
||||
}); |
||||
|
||||
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({ |
||||
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'), |
||||
getDataSourceSrv: () => ({ |
||||
get: async () => ({ |
||||
...defaultDatasource, |
||||
variables: { |
||||
getType: () => VariableSupportType.Custom, |
||||
query: jest.fn(), |
||||
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor), |
||||
}, |
||||
}), |
||||
getList: () => [defaultDatasource, promDatasource], |
||||
getInstanceSettings: () => ({ ...defaultDatasource }), |
||||
}), |
||||
})); |
||||
|
||||
const runRequestMock = jest.fn().mockReturnValue( |
||||
of<PanelData>({ |
||||
state: LoadingState.Done, |
||||
series: [ |
||||
toDataFrame({ |
||||
fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }], |
||||
}), |
||||
], |
||||
timeRange: getDefaultTimeRange(), |
||||
}) |
||||
); |
||||
|
||||
setRunRequest(runRequestMock); |
||||
|
||||
describe('QueryVariableEditorForm', () => { |
||||
const mockOnDataSourceChange = jest.fn(); |
||||
const mockOnQueryChange = jest.fn(); |
||||
const mockOnLegacyQueryChange = jest.fn(); |
||||
const mockOnRegExChange = jest.fn(); |
||||
const mockOnSortChange = jest.fn(); |
||||
const mockOnRefreshChange = jest.fn(); |
||||
const mockOnMultiChange = jest.fn(); |
||||
const mockOnIncludeAllChange = jest.fn(); |
||||
const mockOnAllValueChange = jest.fn(); |
||||
|
||||
const defaultProps = { |
||||
datasource: new MockDataSourceApi(promDatasource.name, undefined, promDatasource.meta), |
||||
onDataSourceChange: mockOnDataSourceChange, |
||||
query: 'my-query', |
||||
onQueryChange: mockOnQueryChange, |
||||
onLegacyQueryChange: mockOnLegacyQueryChange, |
||||
timeRange: getDefaultTimeRange(), |
||||
VariableQueryEditor: LegacyVariableQueryEditor, |
||||
regex: '.*', |
||||
onRegExChange: mockOnRegExChange, |
||||
sort: VariableSort.alphabeticalAsc, |
||||
onSortChange: mockOnSortChange, |
||||
refresh: VariableRefresh.onDashboardLoad, |
||||
onRefreshChange: mockOnRefreshChange, |
||||
isMulti: true, |
||||
onMultiChange: mockOnMultiChange, |
||||
includeAll: true, |
||||
onIncludeAllChange: mockOnIncludeAllChange, |
||||
allValue: 'custom all value', |
||||
onAllValueChange: mockOnAllValueChange, |
||||
}; |
||||
|
||||
function setup(props?: React.ComponentProps<typeof QueryVariableEditorForm>) { |
||||
return { |
||||
renderer: render(<QueryVariableEditorForm {...defaultProps} {...props} />), |
||||
user: userEvent.setup(), |
||||
}; |
||||
} |
||||
|
||||
afterEach(() => { |
||||
jest.clearAllMocks(); |
||||
}); |
||||
|
||||
it('should render the component with initializing the components correctly', () => { |
||||
const { |
||||
renderer: { getByTestId, getByRole }, |
||||
} = setup(); |
||||
const dataSourcePicker = getByTestId(selectors.components.DataSourcePicker.container); |
||||
//const queryEditor = getByTestId('query-editor');
|
||||
const regexInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2 |
||||
); |
||||
const sortSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2 |
||||
); |
||||
const refreshSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2 |
||||
); |
||||
|
||||
const multiSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch |
||||
); |
||||
const includeAllSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch |
||||
); |
||||
const allValueInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput |
||||
); |
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument(); |
||||
expect(dataSourcePicker).toHaveTextContent('Default Test Data Source'); |
||||
expect(regexInput).toBeInTheDocument(); |
||||
expect(regexInput).toHaveValue('.*'); |
||||
expect(sortSelect).toBeInTheDocument(); |
||||
expect(sortSelect).toHaveTextContent('Alphabetical (asc)'); |
||||
expect(refreshSelect).toBeInTheDocument(); |
||||
expect(getByRole('radio', { name: 'On dashboard load' })).toBeChecked(); |
||||
expect(multiSwitch).toBeInTheDocument(); |
||||
expect(multiSwitch).toBeChecked(); |
||||
expect(includeAllSwitch).toBeInTheDocument(); |
||||
expect(includeAllSwitch).toBeChecked(); |
||||
expect(allValueInput).toBeInTheDocument(); |
||||
expect(allValueInput).toHaveValue('custom all value'); |
||||
}); |
||||
|
||||
it('should call onDataSourceChange when changing the datasource', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const dataSourcePicker = getByTestId(selectors.components.DataSourcePicker.inputV2); |
||||
await waitFor(async () => { |
||||
await userEvent.click(dataSourcePicker); // open the select
|
||||
await userEvent.tab(); |
||||
}); |
||||
expect(mockOnDataSourceChange).toHaveBeenCalledTimes(1); |
||||
expect(mockOnDataSourceChange).toHaveBeenCalledWith(defaultDatasource); |
||||
}); |
||||
|
||||
it('should call onQueryChange when changing the query', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const queryEditor = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await userEvent.type(queryEditor, '-new'); |
||||
await userEvent.tab(); |
||||
}); |
||||
|
||||
expect(mockOnLegacyQueryChange).toHaveBeenCalledTimes(1); |
||||
expect(mockOnLegacyQueryChange).toHaveBeenCalledWith('my-query-new', expect.anything()); |
||||
}); |
||||
|
||||
it('should call onRegExChange when changing the regex', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const regexInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2 |
||||
); |
||||
await userEvent.type(regexInput, '{backspace}?'); |
||||
await userEvent.tab(); |
||||
expect(mockOnRegExChange).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
((mockOnRegExChange.mock.calls[0][0] as FormEvent<HTMLTextAreaElement>).target as HTMLTextAreaElement).value |
||||
).toBe('.?'); |
||||
}); |
||||
|
||||
it('should call onSortChange when changing the sort', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const sortSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2 |
||||
); |
||||
await userEvent.click(sortSelect); // open the select
|
||||
const anotherOption = await screen.getByText('Alphabetical (desc)'); |
||||
await userEvent.click(anotherOption); |
||||
|
||||
expect(mockOnSortChange).toHaveBeenCalledTimes(1); |
||||
expect(mockOnSortChange).toHaveBeenCalledWith( |
||||
expect.objectContaining({ value: VariableSort.alphabeticalDesc }), |
||||
expect.anything() |
||||
); |
||||
}); |
||||
|
||||
it('should call onRefreshChange when changing the refresh', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const refreshSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2 |
||||
); |
||||
await userEvent.click(refreshSelect); // open the select
|
||||
const anotherOption = await screen.getByText('On time range change'); |
||||
await userEvent.click(anotherOption); |
||||
|
||||
expect(mockOnRefreshChange).toHaveBeenCalledTimes(1); |
||||
expect(mockOnRefreshChange).toHaveBeenCalledWith(VariableRefresh.onTimeRangeChanged); |
||||
}); |
||||
|
||||
it('should call onMultiChange when changing the multi switch', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const multiSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch |
||||
); |
||||
await userEvent.click(multiSwitch); |
||||
expect(mockOnMultiChange).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
(mockOnMultiChange.mock.calls[0][0] as FormEvent<HTMLInputElement>).target as HTMLInputElement |
||||
).toBeChecked(); |
||||
}); |
||||
|
||||
it('should call onIncludeAllChange when changing the include all switch', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const includeAllSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch |
||||
); |
||||
await userEvent.click(includeAllSwitch); |
||||
expect(mockOnIncludeAllChange).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
(mockOnIncludeAllChange.mock.calls[0][0] as FormEvent<HTMLInputElement>).target as HTMLInputElement |
||||
).toBeChecked(); |
||||
}); |
||||
|
||||
it('should call onAllValueChange when changing the all value', async () => { |
||||
const { |
||||
renderer: { getByTestId }, |
||||
} = setup(); |
||||
const allValueInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput |
||||
); |
||||
await userEvent.type(allValueInput, ' and another value'); |
||||
await userEvent.tab(); |
||||
expect(mockOnAllValueChange).toHaveBeenCalledTimes(1); |
||||
expect( |
||||
((mockOnAllValueChange.mock.calls[0][0] as FormEvent<HTMLInputElement>).target as HTMLInputElement).value |
||||
).toBe('custom all value and another value'); |
||||
}); |
||||
}); |
@ -0,0 +1,128 @@ |
||||
import React, { FormEvent } from 'react'; |
||||
|
||||
import { DataSourceApi, DataSourceInstanceSettings, SelectableValue, TimeRange } from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { QueryVariable } from '@grafana/scenes'; |
||||
import { VariableRefresh, VariableSort } from '@grafana/schema'; |
||||
import { Field } from '@grafana/ui'; |
||||
import { QueryEditor } from 'app/features/dashboard-scene/settings/variables/components/QueryEditor'; |
||||
import { SelectionOptionsForm } from 'app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm'; |
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; |
||||
import { QueryVariableRefreshSelect } from 'app/features/variables/query/QueryVariableRefreshSelect'; |
||||
import { QueryVariableSortSelect } from 'app/features/variables/query/QueryVariableSortSelect'; |
||||
import { VariableQueryEditorType } from 'app/features/variables/types'; |
||||
|
||||
import { VariableLegend } from './VariableLegend'; |
||||
import { VariableTextAreaField } from './VariableTextAreaField'; |
||||
|
||||
type VariableQueryType = QueryVariable['state']['query']; |
||||
|
||||
interface QueryVariableEditorFormProps { |
||||
datasource: DataSourceApi | undefined; |
||||
onDataSourceChange: (dsSettings: DataSourceInstanceSettings) => void; |
||||
query: VariableQueryType; |
||||
onQueryChange: (query: VariableQueryType) => void; |
||||
onLegacyQueryChange: (query: VariableQueryType, definition: string) => void; |
||||
VariableQueryEditor: VariableQueryEditorType | undefined; |
||||
timeRange: TimeRange; |
||||
regex: string | null; |
||||
onRegExChange: (event: FormEvent<HTMLTextAreaElement>) => void; |
||||
sort: VariableSort; |
||||
onSortChange: (option: SelectableValue<VariableSort>) => void; |
||||
refresh: VariableRefresh; |
||||
onRefreshChange: (option: VariableRefresh) => void; |
||||
isMulti: boolean; |
||||
onMultiChange: (event: FormEvent<HTMLInputElement>) => void; |
||||
includeAll: boolean; |
||||
onIncludeAllChange: (event: FormEvent<HTMLInputElement>) => void; |
||||
allValue: string; |
||||
onAllValueChange: (event: FormEvent<HTMLInputElement>) => void; |
||||
} |
||||
|
||||
export function QueryVariableEditorForm({ |
||||
datasource, |
||||
onDataSourceChange, |
||||
query, |
||||
onQueryChange, |
||||
onLegacyQueryChange, |
||||
VariableQueryEditor, |
||||
timeRange, |
||||
regex, |
||||
onRegExChange, |
||||
sort, |
||||
onSortChange, |
||||
refresh, |
||||
onRefreshChange, |
||||
isMulti, |
||||
onMultiChange, |
||||
includeAll, |
||||
onIncludeAllChange, |
||||
allValue, |
||||
onAllValueChange, |
||||
}: QueryVariableEditorFormProps) { |
||||
return ( |
||||
<> |
||||
<VariableLegend>Query options</VariableLegend> |
||||
<Field label="Data source" htmlFor="data-source-picker"> |
||||
<DataSourcePicker current={datasource} onChange={onDataSourceChange} variables={true} width={30} /> |
||||
</Field> |
||||
|
||||
{datasource && VariableQueryEditor && ( |
||||
<QueryEditor |
||||
onQueryChange={onQueryChange} |
||||
onLegacyQueryChange={onLegacyQueryChange} |
||||
datasource={datasource} |
||||
query={query} |
||||
VariableQueryEditor={VariableQueryEditor} |
||||
timeRange={timeRange} |
||||
/> |
||||
)} |
||||
|
||||
<VariableTextAreaField |
||||
defaultValue={regex ?? ''} |
||||
name="Regex" |
||||
description={ |
||||
<div> |
||||
Optional, if you want to extract part of a series name or metric node segment. |
||||
<br /> |
||||
Named capture groups can be used to separate the display text and value ( |
||||
<a |
||||
className="external-link" |
||||
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups" |
||||
target="__blank" |
||||
> |
||||
see examples |
||||
</a> |
||||
). |
||||
</div> |
||||
} |
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/" |
||||
onBlur={onRegExChange} |
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2} |
||||
width={52} |
||||
/> |
||||
|
||||
<QueryVariableSortSelect |
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2} |
||||
onChange={onSortChange} |
||||
sort={sort} |
||||
/> |
||||
|
||||
<QueryVariableRefreshSelect |
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2} |
||||
onChange={onRefreshChange} |
||||
refresh={refresh} |
||||
/> |
||||
|
||||
<VariableLegend>Selection options</VariableLegend> |
||||
<SelectionOptionsForm |
||||
multi={!!isMulti} |
||||
includeAll={!!includeAll} |
||||
allValue={allValue} |
||||
onMultiChange={onMultiChange} |
||||
onIncludeAllChange={onIncludeAllChange} |
||||
onAllValueChange={onAllValueChange} |
||||
/> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,288 @@ |
||||
import { getByRole, render, screen, act, waitFor } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React from 'react'; |
||||
import { lastValueFrom, of } from 'rxjs'; |
||||
|
||||
import { |
||||
VariableSupportType, |
||||
PanelData, |
||||
LoadingState, |
||||
toDataFrame, |
||||
getDefaultTimeRange, |
||||
FieldType, |
||||
} from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { setRunRequest } from '@grafana/runtime'; |
||||
import { QueryVariable } from '@grafana/scenes'; |
||||
import { VariableRefresh, VariableSort } from '@grafana/schema'; |
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks'; |
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor'; |
||||
|
||||
import { QueryVariableEditor } from './QueryVariableEditor'; |
||||
|
||||
const defaultDatasource = mockDataSource({ |
||||
name: 'Default Test Data Source', |
||||
type: 'test', |
||||
}); |
||||
|
||||
const promDatasource = mockDataSource({ |
||||
name: 'Prometheus', |
||||
type: 'prometheus', |
||||
}); |
||||
|
||||
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({ |
||||
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'), |
||||
getDataSourceSrv: () => ({ |
||||
get: async () => ({ |
||||
...defaultDatasource, |
||||
variables: { |
||||
getType: () => VariableSupportType.Custom, |
||||
query: jest.fn(), |
||||
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor), |
||||
}, |
||||
}), |
||||
getList: () => [defaultDatasource, promDatasource], |
||||
getInstanceSettings: () => ({ ...defaultDatasource }), |
||||
}), |
||||
})); |
||||
|
||||
const runRequestMock = jest.fn().mockReturnValue( |
||||
of<PanelData>({ |
||||
state: LoadingState.Done, |
||||
series: [ |
||||
toDataFrame({ |
||||
fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }], |
||||
}), |
||||
], |
||||
timeRange: getDefaultTimeRange(), |
||||
}) |
||||
); |
||||
|
||||
setRunRequest(runRequestMock); |
||||
|
||||
describe('QueryVariableEditor', () => { |
||||
const onRunQueryMock = jest.fn(); |
||||
|
||||
async function setup(props?: React.ComponentProps<typeof QueryVariableEditor>) { |
||||
const variable = new QueryVariable({ |
||||
datasource: { |
||||
uid: defaultDatasource.uid, |
||||
type: defaultDatasource.type, |
||||
}, |
||||
query: 'my-query', |
||||
regex: '.*', |
||||
sort: VariableSort.alphabeticalAsc, |
||||
refresh: VariableRefresh.onDashboardLoad, |
||||
isMulti: true, |
||||
includeAll: true, |
||||
allValue: 'custom all value', |
||||
}); |
||||
|
||||
return { |
||||
renderer: await act(() => { |
||||
return render(<QueryVariableEditor variable={variable} onRunQuery={onRunQueryMock} />); |
||||
}), |
||||
variable, |
||||
user: userEvent.setup(), |
||||
}; |
||||
} |
||||
|
||||
afterEach(() => { |
||||
jest.clearAllMocks(); |
||||
}); |
||||
|
||||
it('should render the component with initializing the components correctly', async () => { |
||||
const { renderer } = await setup(); |
||||
const dataSourcePicker = renderer.getByTestId(selectors.components.DataSourcePicker.container); |
||||
const queryEditor = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput |
||||
); |
||||
const regexInput = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2 |
||||
); |
||||
const sortSelect = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2 |
||||
); |
||||
const refreshSelect = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2 |
||||
); |
||||
|
||||
const multiSwitch = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch |
||||
); |
||||
const includeAllSwitch = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch |
||||
); |
||||
const allValueInput = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput |
||||
); |
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument(); |
||||
expect(dataSourcePicker).toHaveTextContent('Default Test Data Source'); |
||||
expect(queryEditor).toBeInTheDocument(); |
||||
expect(queryEditor).toHaveValue('my-query'); |
||||
expect(regexInput).toBeInTheDocument(); |
||||
expect(regexInput).toHaveValue('.*'); |
||||
expect(sortSelect).toBeInTheDocument(); |
||||
expect(sortSelect).toHaveTextContent('Alphabetical (asc)'); |
||||
expect(refreshSelect).toBeInTheDocument(); |
||||
expect(getByRole(refreshSelect, 'radio', { name: 'On dashboard load' })).toBeChecked(); |
||||
expect(multiSwitch).toBeInTheDocument(); |
||||
expect(multiSwitch).toBeChecked(); |
||||
expect(includeAllSwitch).toBeInTheDocument(); |
||||
expect(includeAllSwitch).toBeChecked(); |
||||
expect(allValueInput).toBeInTheDocument(); |
||||
expect(allValueInput).toHaveValue('custom all value'); |
||||
}); |
||||
|
||||
it('should update variable state when changing the datasource', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const dataSourcePicker = getByTestId(selectors.components.DataSourcePicker.container).getElementsByTagName('input'); |
||||
|
||||
await waitFor(async () => { |
||||
await user.type(dataSourcePicker[0], 'm'); |
||||
await user.tab(); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.datasource).toEqual({ uid: 'mock-ds-2', type: 'test' }); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the query', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const queryEditor = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.type(queryEditor, '-new'); |
||||
await user.tab(); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.query).toEqual('my-query-new'); |
||||
expect(onRunQueryMock).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the regex', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const regexInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2 |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.type(regexInput, '{backspace}?'); |
||||
await user.tab(); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.regex).toBe('.?'); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the sort', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const sortSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2 |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.click(sortSelect); |
||||
const anotherOption = await screen.getByText('Alphabetical (desc)'); |
||||
await user.click(anotherOption); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.sort).toBe(VariableSort.alphabeticalDesc); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the refresh', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const refreshSelect = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2 |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.click(refreshSelect); |
||||
const anotherOption = await screen.getByText('On time range change'); |
||||
await user.click(anotherOption); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.refresh).toBe(VariableRefresh.onTimeRangeChanged); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the multi switch', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const multiSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.click(multiSwitch); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.isMulti).toBe(false); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the include all switch', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const includeAllSwitch = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.click(includeAllSwitch); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.includeAll).toBe(false); |
||||
}); |
||||
|
||||
it('should update the variable state when changing the all value', async () => { |
||||
const { |
||||
variable, |
||||
renderer: { getByTestId }, |
||||
user, |
||||
} = await setup(); |
||||
const allValueInput = getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput |
||||
); |
||||
|
||||
await waitFor(async () => { |
||||
await user.type(allValueInput, ' and another value'); |
||||
await user.tab(); |
||||
await lastValueFrom(variable.validateAndUpdate()); |
||||
}); |
||||
|
||||
expect(variable.state.allValue).toBe('custom all value and another value'); |
||||
}); |
||||
}); |
@ -1,12 +1,80 @@ |
||||
import React from 'react'; |
||||
import React, { FormEvent } from 'react'; |
||||
import { useAsync } from 'react-use'; |
||||
|
||||
import { QueryVariable } from '@grafana/scenes'; |
||||
import { SelectableValue, DataSourceInstanceSettings } from '@grafana/data'; |
||||
import { getDataSourceSrv } from '@grafana/runtime'; |
||||
import { QueryVariable, sceneGraph } from '@grafana/scenes'; |
||||
import { DataSourceRef, VariableRefresh, VariableSort } from '@grafana/schema'; |
||||
import { getVariableQueryEditor } from 'app/features/variables/editor/getVariableQueryEditor'; |
||||
|
||||
import { QueryVariableEditorForm } from '../components/QueryVariableForm'; |
||||
|
||||
interface QueryVariableEditorProps { |
||||
variable: QueryVariable; |
||||
onChange: (variable: QueryVariable) => void; |
||||
onRunQuery: () => void; |
||||
} |
||||
type VariableQueryType = QueryVariable['state']['query']; |
||||
|
||||
export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEditorProps) { |
||||
const { datasource: datasourceRef, regex, sort, refresh, isMulti, includeAll, allValue, query } = variable.useState(); |
||||
const { value: timeRange } = sceneGraph.getTimeRange(variable).useState(); |
||||
|
||||
const { value: dsConfig } = useAsync(async () => { |
||||
const datasource = await getDataSourceSrv().get(datasourceRef ?? ''); |
||||
const VariableQueryEditor = await getVariableQueryEditor(datasource); |
||||
|
||||
return { datasource, VariableQueryEditor }; |
||||
}, [datasourceRef]); |
||||
const { datasource, VariableQueryEditor } = dsConfig ?? {}; |
||||
|
||||
const onRegExChange = (event: React.FormEvent<HTMLTextAreaElement>) => { |
||||
variable.setState({ regex: event.currentTarget.value }); |
||||
}; |
||||
const onSortChange = (sort: SelectableValue<VariableSort>) => { |
||||
variable.setState({ sort: sort.value }); |
||||
}; |
||||
const onRefreshChange = (refresh: VariableRefresh) => { |
||||
variable.setState({ refresh: refresh }); |
||||
}; |
||||
const onMultiChange = (event: FormEvent<HTMLInputElement>) => { |
||||
variable.setState({ isMulti: event.currentTarget.checked }); |
||||
}; |
||||
const onIncludeAllChange = (event: FormEvent<HTMLInputElement>) => { |
||||
variable.setState({ includeAll: event.currentTarget.checked }); |
||||
}; |
||||
const onAllValueChange = (event: FormEvent<HTMLInputElement>) => { |
||||
variable.setState({ allValue: event.currentTarget.value }); |
||||
}; |
||||
const onDataSourceChange = (dsInstanceSettings: DataSourceInstanceSettings) => { |
||||
const datasource: DataSourceRef = { uid: dsInstanceSettings.uid, type: dsInstanceSettings.type }; |
||||
variable.setState({ datasource }); |
||||
}; |
||||
const onQueryChange = (query: VariableQueryType) => { |
||||
variable.setState({ query }); |
||||
onRunQuery(); |
||||
}; |
||||
|
||||
export function QueryVariableEditor(props: QueryVariableEditorProps) { |
||||
return <div>QueryVariableEditor</div>; |
||||
return ( |
||||
<QueryVariableEditorForm |
||||
datasource={datasource} |
||||
onDataSourceChange={onDataSourceChange} |
||||
query={query} |
||||
onQueryChange={onQueryChange} |
||||
onLegacyQueryChange={onQueryChange} |
||||
VariableQueryEditor={VariableQueryEditor} |
||||
timeRange={timeRange} |
||||
regex={regex} |
||||
onRegExChange={onRegExChange} |
||||
sort={sort} |
||||
onSortChange={onSortChange} |
||||
refresh={refresh} |
||||
onRefreshChange={onRefreshChange} |
||||
isMulti={!!isMulti} |
||||
onMultiChange={onMultiChange} |
||||
includeAll={!!includeAll} |
||||
onIncludeAllChange={onIncludeAllChange} |
||||
allValue={allValue ?? ''} |
||||
onAllValueChange={onAllValueChange} |
||||
/> |
||||
); |
||||
} |
||||
|
Loading…
Reference in new issue