mirror of https://github.com/grafana/grafana
Dashboards: Use `auto` and only use `AdHocFiltersVariable` to manage filters (#81318)
parent
63670b7adc
commit
3d86d101b7
@ -0,0 +1,75 @@ |
||||
import { act, render } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React from 'react'; |
||||
|
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks'; |
||||
|
||||
import { AdHocVariableForm } from './AdHocVariableForm'; |
||||
|
||||
const defaultDatasource = mockDataSource({ |
||||
name: 'Default Test Data Source', |
||||
uid: 'test-ds', |
||||
type: 'test', |
||||
}); |
||||
|
||||
const promDatasource = mockDataSource({ |
||||
name: 'Prometheus', |
||||
uid: 'prometheus', |
||||
type: 'prometheus', |
||||
}); |
||||
|
||||
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({ |
||||
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'), |
||||
getDataSourceSrv: () => ({ |
||||
get: async () => defaultDatasource, |
||||
getList: () => [defaultDatasource, promDatasource], |
||||
getInstanceSettings: () => ({ ...defaultDatasource }), |
||||
}), |
||||
})); |
||||
|
||||
describe('AdHocVariableForm', () => { |
||||
it('should render the form with the provided data source', async () => { |
||||
const onDataSourceChange = jest.fn(); |
||||
const { renderer } = await setup({ |
||||
datasource: defaultDatasource, |
||||
onDataSourceChange, |
||||
infoText: 'Test Info', |
||||
}); |
||||
|
||||
const dataSourcePicker = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect |
||||
); |
||||
const infoText = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText |
||||
); |
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument(); |
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source'); |
||||
expect(infoText).toBeInTheDocument(); |
||||
expect(infoText).toHaveTextContent('Test Info'); |
||||
}); |
||||
|
||||
it('should call the onDataSourceChange callback when the data source is changed', async () => { |
||||
const onDataSourceChange = jest.fn(); |
||||
const { renderer, user } = await setup({ |
||||
datasource: defaultDatasource, |
||||
onDataSourceChange, |
||||
infoText: 'Test Info', |
||||
}); |
||||
|
||||
// Simulate changing the data source
|
||||
await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2)); |
||||
await user.click(renderer.getByText(/prom/i)); |
||||
|
||||
expect(onDataSourceChange).toHaveBeenCalledTimes(1); |
||||
expect(onDataSourceChange).toHaveBeenCalledWith(promDatasource, undefined); |
||||
}); |
||||
}); |
||||
|
||||
async function setup(props?: React.ComponentProps<typeof AdHocVariableForm>) { |
||||
return { |
||||
renderer: await act(() => render(<AdHocVariableForm onDataSourceChange={jest.fn()} {...props} />)), |
||||
user: userEvent.setup(), |
||||
}; |
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
import React from 'react'; |
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { DataSourceRef } from '@grafana/schema'; |
||||
import { Alert, Field } from '@grafana/ui'; |
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; |
||||
|
||||
import { VariableLegend } from './VariableLegend'; |
||||
|
||||
interface AdHocVariableFormProps { |
||||
datasource?: DataSourceRef; |
||||
onDataSourceChange: (dsSettings: DataSourceInstanceSettings) => void; |
||||
infoText?: string; |
||||
} |
||||
|
||||
export function AdHocVariableForm({ datasource, infoText, onDataSourceChange }: AdHocVariableFormProps) { |
||||
return ( |
||||
<> |
||||
<VariableLegend>Ad-hoc options</VariableLegend> |
||||
<Field label="Data source" htmlFor="data-source-picker"> |
||||
<DataSourcePicker current={datasource} onChange={onDataSourceChange} width={30} variables={true} noDefault /> |
||||
</Field> |
||||
|
||||
{infoText ? ( |
||||
<Alert |
||||
title={infoText} |
||||
severity="info" |
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText} |
||||
/> |
||||
) : null} |
||||
</> |
||||
); |
||||
} |
||||
@ -0,0 +1,122 @@ |
||||
import { render, act } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React from 'react'; |
||||
import { of } from 'rxjs'; |
||||
|
||||
import { |
||||
FieldType, |
||||
LoadingState, |
||||
PanelData, |
||||
VariableSupportType, |
||||
getDefaultTimeRange, |
||||
toDataFrame, |
||||
} from '@grafana/data'; |
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { setRunRequest } from '@grafana/runtime/src'; |
||||
import { AdHocFiltersVariable } from '@grafana/scenes'; |
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks'; |
||||
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor'; |
||||
|
||||
import { AdHocFiltersVariableEditor } from './AdHocFiltersVariableEditor'; |
||||
|
||||
const defaultDatasource = mockDataSource({ |
||||
name: 'Default Test Data Source', |
||||
uid: 'test-ds', |
||||
type: 'test', |
||||
}); |
||||
|
||||
const promDatasource = mockDataSource({ |
||||
name: 'Prometheus', |
||||
uid: '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('AdHocFiltersVariableEditor', () => { |
||||
it('renders AdHocVariableForm with correct props', async () => { |
||||
const { renderer } = await setup(); |
||||
const dataSourcePicker = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect |
||||
); |
||||
const infoText = renderer.getByTestId( |
||||
selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText |
||||
); |
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument(); |
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source'); |
||||
expect(infoText).toBeInTheDocument(); |
||||
expect(infoText).toHaveTextContent('This data source does not support ad hoc filters yet.'); |
||||
}); |
||||
|
||||
it('should update the variable data source when data source picker is changed', async () => { |
||||
const { renderer, variable, user } = await setup(); |
||||
|
||||
// Simulate changing the data source
|
||||
await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2)); |
||||
await user.click(renderer.getByText(/prom/i)); |
||||
|
||||
expect(variable.state.datasource).toEqual({ uid: 'prometheus', type: 'prometheus' }); |
||||
}); |
||||
}); |
||||
|
||||
async function setup(props?: React.ComponentProps<typeof AdHocFiltersVariableEditor>) { |
||||
const onRunQuery = jest.fn(); |
||||
const variable = new AdHocFiltersVariable({ |
||||
name: 'adhocVariable', |
||||
type: 'adhoc', |
||||
label: 'Ad hoc filters', |
||||
description: 'Ad hoc filters are applied automatically to all queries that target this data source', |
||||
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type }, |
||||
filters: [ |
||||
{ |
||||
key: 'test', |
||||
operator: '=', |
||||
value: 'testValue', |
||||
}, |
||||
], |
||||
baseFilters: [ |
||||
{ |
||||
key: 'baseTest', |
||||
operator: '=', |
||||
value: 'baseTestValue', |
||||
}, |
||||
], |
||||
}); |
||||
return { |
||||
renderer: await act(() => |
||||
render(<AdHocFiltersVariableEditor variable={variable} onRunQuery={onRunQuery} {...props} />) |
||||
), |
||||
variable, |
||||
user: userEvent.setup(), |
||||
mocks: { onRunQuery }, |
||||
}; |
||||
} |
||||
@ -1,12 +1,40 @@ |
||||
import React from 'react'; |
||||
import { useAsync } from 'react-use'; |
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data'; |
||||
import { getDataSourceSrv } from '@grafana/runtime'; |
||||
import { AdHocFiltersVariable } from '@grafana/scenes'; |
||||
import { DataSourceRef } from '@grafana/schema'; |
||||
|
||||
import { AdHocVariableForm } from '../components/AdHocVariableForm'; |
||||
|
||||
interface AdHocFiltersVariableEditorProps { |
||||
variable: AdHocFiltersVariable; |
||||
onChange: (variable: AdHocFiltersVariable) => void; |
||||
onRunQuery: (variable: AdHocFiltersVariable) => void; |
||||
} |
||||
|
||||
export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProps) { |
||||
return <div>AdHocFiltersVariableEditor</div>; |
||||
const { variable } = props; |
||||
const datasourceRef = variable.useState().datasource ?? undefined; |
||||
|
||||
const { value: datasourceSettings } = useAsync(async () => { |
||||
return await getDataSourceSrv().get(datasourceRef); |
||||
}, [datasourceRef]); |
||||
|
||||
const message = datasourceSettings?.getTagKeys |
||||
? 'Ad hoc filters are applied automatically to all queries that target this data source' |
||||
: 'This data source does not support ad hoc filters yet.'; |
||||
|
||||
const onDataSourceChange = (ds: DataSourceInstanceSettings) => { |
||||
const dsRef: DataSourceRef = { |
||||
uid: ds.uid, |
||||
type: ds.type, |
||||
}; |
||||
|
||||
variable.setState({ |
||||
datasource: dsRef, |
||||
}); |
||||
}; |
||||
|
||||
return <AdHocVariableForm datasource={datasourceRef} infoText={message} onDataSourceChange={onDataSourceChange} />; |
||||
} |
||||
|
||||
@ -1,90 +0,0 @@ |
||||
import { render, screen } from '@testing-library/react'; |
||||
import userEvent from '@testing-library/user-event'; |
||||
import React, { ComponentProps } from 'react'; |
||||
|
||||
import { selectors } from '@grafana/e2e-selectors'; |
||||
import { mockDataSource } from 'app/features/alerting/unified/mocks'; |
||||
import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; |
||||
|
||||
import { adHocBuilder } from '../shared/testing/builders'; |
||||
|
||||
import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor'; |
||||
|
||||
const promDsMock = mockDataSource({ |
||||
name: 'Prometheus', |
||||
type: DataSourceType.Prometheus, |
||||
}); |
||||
|
||||
const lokiDsMock = mockDataSource({ |
||||
name: 'Loki', |
||||
type: DataSourceType.Loki, |
||||
}); |
||||
|
||||
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { |
||||
return { |
||||
getDataSourceSrv: () => ({ |
||||
get: () => { |
||||
return Promise.resolve(promDsMock); |
||||
}, |
||||
getList: () => [promDsMock, lokiDsMock], |
||||
getInstanceSettings: (v: string) => { |
||||
if (v === 'Prometheus') { |
||||
return promDsMock; |
||||
} |
||||
return lokiDsMock; |
||||
}, |
||||
}), |
||||
}; |
||||
}); |
||||
|
||||
const props = { |
||||
extended: { |
||||
dataSources: [ |
||||
{ text: 'Prometheus', value: null }, // default datasource
|
||||
{ text: 'Loki', value: { type: 'loki-ds', uid: 'abc' } }, |
||||
], |
||||
} as ComponentProps<typeof AdHocVariableEditor>['extended'], |
||||
variable: adHocBuilder().withId('adhoc').withRootStateKey('key').withName('adhoc').build(), |
||||
onPropChange: jest.fn(), |
||||
|
||||
// connected actions
|
||||
initAdHocVariableEditor: jest.fn(), |
||||
changeVariableDatasource: jest.fn(), |
||||
}; |
||||
|
||||
describe('AdHocVariableEditor', () => { |
||||
beforeEach(() => { |
||||
props.changeVariableDatasource.mockReset(); |
||||
}); |
||||
|
||||
it('has a datasource select menu', async () => { |
||||
render(<AdHocVariableEditor {...props} />); |
||||
|
||||
expect(await screen.getByTestId(selectors.components.DataSourcePicker.container)).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('calls the callback when changing the datasource', async () => { |
||||
render(<AdHocVariableEditor {...props} />); |
||||
const selectEl = screen |
||||
.getByTestId(selectors.components.DataSourcePicker.container) |
||||
.getElementsByTagName('input')[0]; |
||||
await userEvent.click(selectEl); |
||||
await userEvent.click(screen.getByText('Loki')); |
||||
|
||||
expect(props.changeVariableDatasource).toBeCalledWith( |
||||
{ type: 'adhoc', id: 'adhoc', rootStateKey: 'key' }, |
||||
{ type: 'loki', uid: 'mock-ds-3' } |
||||
); |
||||
}); |
||||
|
||||
it('renders informational text', () => { |
||||
const extended = { |
||||
...props.extended, |
||||
infoText: "Here's a message that should help you", |
||||
}; |
||||
render(<AdHocVariableEditor {...props} extended={extended} />); |
||||
|
||||
const alert = screen.getByText("Here's a message that should help you"); |
||||
expect(alert).toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
Loading…
Reference in new issue