mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
Variables: Fixes upgrade of legacy Prometheus queries (#29704)
* Variables: Fixes upgrade of legacy queries * Chore: changes after PR comments
This commit is contained in:
parent
31d64d9074
commit
3f2b28975c
@ -30,8 +30,8 @@ interface DispatchProps {
|
||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||
|
||||
export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
|
||||
async componentDidMount() {
|
||||
await this.props.initDataSourceVariableEditor();
|
||||
componentDidMount() {
|
||||
this.props.initDataSourceVariableEditor();
|
||||
}
|
||||
|
||||
onRegExChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -9,39 +9,47 @@ import {
|
||||
initDataSourceVariableEditor,
|
||||
updateDataSourceVariableOptions,
|
||||
} from './actions';
|
||||
import { DataSourcePluginMeta, DataSourceSelectItem } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, DataSourceJsonData, DataSourcePluginMeta } from '@grafana/data';
|
||||
import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks';
|
||||
import { createDataSourceOptions } from './reducer';
|
||||
import { setCurrentVariableValue, addVariable } from '../state/sharedReducer';
|
||||
import { addVariable, setCurrentVariableValue } from '../state/sharedReducer';
|
||||
import { changeVariableEditorExtended } from '../editor/reducer';
|
||||
import { datasourceBuilder } from '../shared/testing/builders';
|
||||
|
||||
interface Args {
|
||||
sources?: DataSourceInstanceSettings[];
|
||||
query?: string;
|
||||
regex?: string;
|
||||
}
|
||||
|
||||
function getTestContext({ sources = [], query, regex }: Args = {}) {
|
||||
const getListMock = jest.fn().mockReturnValue(sources);
|
||||
const getDatasourceSrvMock = jest.fn().mockReturnValue({ getList: getListMock });
|
||||
const dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrvMock };
|
||||
const datasource = datasourceBuilder()
|
||||
.withId('0')
|
||||
.withQuery(query)
|
||||
.withRegEx(regex)
|
||||
.build();
|
||||
|
||||
return { getListMock, getDatasourceSrvMock, dependencies, datasource };
|
||||
}
|
||||
|
||||
describe('data source actions', () => {
|
||||
variableAdapters.setInit(() => [createDataSourceVariableAdapter()]);
|
||||
|
||||
describe('when updateDataSourceVariableOptions is dispatched', () => {
|
||||
describe('and there is no regex', () => {
|
||||
it('then the correct actions are dispatched', async () => {
|
||||
const sources: DataSourceSelectItem[] = [
|
||||
{
|
||||
name: 'first-name',
|
||||
value: 'first-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
{
|
||||
name: 'second-name',
|
||||
value: 'second-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
const meta = getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' });
|
||||
const sources: DataSourceInstanceSettings[] = [
|
||||
getDataSourceInstanceSetting('first-name', meta),
|
||||
getDataSourceInstanceSetting('second-name', meta),
|
||||
];
|
||||
|
||||
const getMetricSourcesMock = jest.fn().mockResolvedValue(sources);
|
||||
const getDatasourceSrvMock = jest.fn().mockReturnValue({ getMetricSources: getMetricSourcesMock });
|
||||
const dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrvMock };
|
||||
const datasource = datasourceBuilder()
|
||||
.withId('0')
|
||||
.withQuery('mock-data-id')
|
||||
.build();
|
||||
const { datasource, dependencies, getListMock, getDatasourceSrvMock } = getTestContext({
|
||||
sources,
|
||||
query: 'mock-data-id',
|
||||
});
|
||||
|
||||
const tester = await reduxTester<{ templating: TemplatingState }>()
|
||||
.givenRootReducer(getRootReducer())
|
||||
@ -55,7 +63,16 @@ describe('data source actions', () => {
|
||||
|
||||
await tester.thenDispatchedActionsShouldEqual(
|
||||
createDataSourceOptions(
|
||||
toVariablePayload({ type: 'datasource', id: '0' }, { sources, regex: (undefined as unknown) as RegExp })
|
||||
toVariablePayload(
|
||||
{ type: 'datasource', id: '0' },
|
||||
{
|
||||
sources: [
|
||||
{ name: 'first-name', value: 'first-name', meta },
|
||||
{ name: 'second-name', value: 'second-name', meta },
|
||||
],
|
||||
regex: (undefined as unknown) as RegExp,
|
||||
}
|
||||
)
|
||||
),
|
||||
setCurrentVariableValue(
|
||||
toVariablePayload(
|
||||
@ -65,35 +82,26 @@ describe('data source actions', () => {
|
||||
)
|
||||
);
|
||||
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledWith({ skipVariables: true });
|
||||
expect(getListMock).toHaveBeenCalledTimes(1);
|
||||
expect(getListMock).toHaveBeenCalledWith({ metrics: true, variables: false });
|
||||
expect(getDatasourceSrvMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and there is a regex', () => {
|
||||
it('then the correct actions are dispatched', async () => {
|
||||
const sources: DataSourceSelectItem[] = [
|
||||
{
|
||||
name: 'first-name',
|
||||
value: 'first-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
{
|
||||
name: 'second-name',
|
||||
value: 'second-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
const meta = getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' });
|
||||
const sources: DataSourceInstanceSettings[] = [
|
||||
getDataSourceInstanceSetting('first-name', meta),
|
||||
getDataSourceInstanceSetting('second-name', meta),
|
||||
];
|
||||
|
||||
const getMetricSourcesMock = jest.fn().mockResolvedValue(sources);
|
||||
const getDatasourceSrvMock = jest.fn().mockReturnValue({ getMetricSources: getMetricSourcesMock });
|
||||
const dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrvMock };
|
||||
const datasource = datasourceBuilder()
|
||||
.withId('0')
|
||||
.withQuery('mock-data-id')
|
||||
.withRegEx('/.*(second-name).*/')
|
||||
.build();
|
||||
const { datasource, dependencies, getListMock, getDatasourceSrvMock } = getTestContext({
|
||||
sources,
|
||||
query: 'mock-data-id',
|
||||
regex: '/.*(second-name).*/',
|
||||
});
|
||||
|
||||
const tester = await reduxTester<{ templating: TemplatingState }>()
|
||||
.givenRootReducer(getRootReducer())
|
||||
.whenActionIsDispatched(
|
||||
@ -106,7 +114,16 @@ describe('data source actions', () => {
|
||||
|
||||
await tester.thenDispatchedActionsShouldEqual(
|
||||
createDataSourceOptions(
|
||||
toVariablePayload({ type: 'datasource', id: '0' }, { sources, regex: /.*(second-name).*/ })
|
||||
toVariablePayload(
|
||||
{ type: 'datasource', id: '0' },
|
||||
{
|
||||
sources: [
|
||||
{ name: 'first-name', value: 'first-name', meta },
|
||||
{ name: 'second-name', value: 'second-name', meta },
|
||||
],
|
||||
regex: /.*(second-name).*/,
|
||||
}
|
||||
)
|
||||
),
|
||||
setCurrentVariableValue(
|
||||
toVariablePayload(
|
||||
@ -116,8 +133,8 @@ describe('data source actions', () => {
|
||||
)
|
||||
);
|
||||
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledWith({ skipVariables: true });
|
||||
expect(getListMock).toHaveBeenCalledTimes(1);
|
||||
expect(getListMock).toHaveBeenCalledWith({ metrics: true, variables: false });
|
||||
expect(getDatasourceSrvMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -125,49 +142,41 @@ describe('data source actions', () => {
|
||||
|
||||
describe('when initDataSourceVariableEditor is dispatched', () => {
|
||||
it('then the correct actions are dispatched', async () => {
|
||||
const sources: DataSourceSelectItem[] = [
|
||||
{
|
||||
name: 'first-name',
|
||||
value: 'first-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
{
|
||||
name: 'second-name',
|
||||
value: 'second-value',
|
||||
meta: getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' }),
|
||||
},
|
||||
{
|
||||
name: 'mixed-name',
|
||||
value: 'mixed-value',
|
||||
meta: getMockPlugin(({
|
||||
name: 'mixed-data-name',
|
||||
id: 'mixed-data-id',
|
||||
mixed: true,
|
||||
} as unknown) as DataSourcePluginMeta),
|
||||
},
|
||||
const meta = getMockPlugin({ name: 'mock-data-name', id: 'mock-data-id' });
|
||||
const sources: DataSourceInstanceSettings[] = [
|
||||
getDataSourceInstanceSetting('first-name', meta),
|
||||
getDataSourceInstanceSetting('second-name', meta),
|
||||
];
|
||||
|
||||
const getMetricSourcesMock = jest.fn().mockResolvedValue(sources);
|
||||
const getDatasourceSrvMock = jest.fn().mockReturnValue({ getMetricSources: getMetricSourcesMock });
|
||||
const dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrvMock };
|
||||
const { dependencies, getListMock, getDatasourceSrvMock } = getTestContext({ sources });
|
||||
|
||||
const tester = await reduxTester<{ templating: TemplatingState }>()
|
||||
await reduxTester<{ templating: TemplatingState }>()
|
||||
.givenRootReducer(getRootReducer())
|
||||
.whenAsyncActionIsDispatched(initDataSourceVariableEditor(dependencies));
|
||||
.whenActionIsDispatched(initDataSourceVariableEditor(dependencies))
|
||||
.thenDispatchedActionsShouldEqual(
|
||||
changeVariableEditorExtended({
|
||||
propName: 'dataSourceTypes',
|
||||
propValue: [
|
||||
{ text: '', value: '' },
|
||||
{ text: 'mock-data-name', value: 'mock-data-id' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
await tester.thenDispatchedActionsShouldEqual(
|
||||
changeVariableEditorExtended({
|
||||
propName: 'dataSourceTypes',
|
||||
propValue: [
|
||||
{ text: '', value: '' },
|
||||
{ text: 'mock-data-name', value: 'mock-data-id' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getMetricSourcesMock).toHaveBeenCalledWith();
|
||||
expect(getListMock).toHaveBeenCalledTimes(1);
|
||||
expect(getListMock).toHaveBeenCalledWith({ metrics: true, variables: true });
|
||||
expect(getDatasourceSrvMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getDataSourceInstanceSetting(name: string, meta: DataSourcePluginMeta): DataSourceInstanceSettings {
|
||||
return {
|
||||
id: 1,
|
||||
uid: '',
|
||||
type: '',
|
||||
name,
|
||||
meta,
|
||||
jsonData: ({} as unknown) as DataSourceJsonData,
|
||||
};
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { toVariablePayload, VariableIdentifier } from '../state/types';
|
||||
import { ThunkResult } from '../../../types';
|
||||
import { createDataSourceOptions } from './reducer';
|
||||
import { validateVariableSelectionState } from '../state/actions';
|
||||
import { DataSourceSelectItem, stringToJsRegex } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, stringToJsRegex } from '@grafana/data';
|
||||
import { getDatasourceSrv } from '../../plugins/datasource_srv';
|
||||
import { getVariable } from '../state/selectors';
|
||||
import { DataSourceVariableModel } from '../types';
|
||||
@ -18,7 +18,10 @@ export const updateDataSourceVariableOptions = (
|
||||
identifier: VariableIdentifier,
|
||||
dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrv }
|
||||
): ThunkResult<void> => async (dispatch, getState) => {
|
||||
const sources = await dependencies.getDatasourceSrv().getMetricSources({ skipVariables: true });
|
||||
const sources = dependencies
|
||||
.getDatasourceSrv()
|
||||
.getList({ metrics: true, variables: false })
|
||||
.map(toDataSourceSelectItem);
|
||||
const variableInState = getVariable<DataSourceVariableModel>(identifier.id, getState());
|
||||
let regex;
|
||||
|
||||
@ -27,16 +30,18 @@ export const updateDataSourceVariableOptions = (
|
||||
regex = stringToJsRegex(regex);
|
||||
}
|
||||
|
||||
await dispatch(createDataSourceOptions(toVariablePayload(identifier, { sources, regex })));
|
||||
dispatch(createDataSourceOptions(toVariablePayload(identifier, { sources, regex })));
|
||||
await dispatch(validateVariableSelectionState(identifier));
|
||||
};
|
||||
|
||||
export const initDataSourceVariableEditor = (
|
||||
dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrv }
|
||||
): ThunkResult<void> => async dispatch => {
|
||||
const dataSources: DataSourceSelectItem[] = await dependencies.getDatasourceSrv().getMetricSources();
|
||||
const filtered = dataSources.filter(ds => !ds.meta.mixed && ds.value !== null);
|
||||
const dataSourceTypes = _(filtered)
|
||||
): ThunkResult<void> => dispatch => {
|
||||
const dataSources = dependencies
|
||||
.getDatasourceSrv()
|
||||
.getList({ metrics: true, variables: true })
|
||||
.map(toDataSourceSelectItem);
|
||||
const dataSourceTypes = _(dataSources)
|
||||
.uniqBy('meta.id')
|
||||
.map((ds: any) => {
|
||||
return { text: ds.meta.name, value: ds.meta.id };
|
||||
@ -52,3 +57,11 @@ export const initDataSourceVariableEditor = (
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
function toDataSourceSelectItem(setting: DataSourceInstanceSettings) {
|
||||
return {
|
||||
name: setting.name,
|
||||
value: setting.name,
|
||||
meta: setting.meta,
|
||||
};
|
||||
}
|
||||
|
@ -103,7 +103,6 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult<void
|
||||
const variables = getVariables(getState());
|
||||
for (let index = 0; index < variables.length; index++) {
|
||||
const variable = variables[index];
|
||||
dispatch(upgradeLegacyQueries(toVariableIdentifier(variable)));
|
||||
dispatch(variableStateNotStarted(toVariablePayload(variable)));
|
||||
}
|
||||
};
|
||||
@ -427,9 +426,9 @@ export const setOptionAsCurrent = (
|
||||
current: VariableOption,
|
||||
emitChanges: boolean
|
||||
): ThunkResult<Promise<void>> => {
|
||||
return dispatch => {
|
||||
return async dispatch => {
|
||||
dispatch(setCurrentVariableValue(toVariablePayload(identifier, { option: current })));
|
||||
return dispatch(variableUpdated(identifier, emitChanges));
|
||||
return await dispatch(variableUpdated(identifier, emitChanges));
|
||||
};
|
||||
};
|
||||
|
||||
@ -459,13 +458,14 @@ export const variableUpdated = (
|
||||
identifier: VariableIdentifier,
|
||||
emitChangeEvents: boolean
|
||||
): ThunkResult<Promise<void>> => {
|
||||
return (dispatch, getState) => {
|
||||
return async (dispatch, getState) => {
|
||||
const variableInState = getVariable(identifier.id, getState());
|
||||
|
||||
// if we're initializing variables ignore cascading update because we are in a boot up scenario
|
||||
if (getState().templating.transaction.status === TransactionStatus.Fetching) {
|
||||
if (getVariableRefresh(variableInState) === VariableRefresh.never) {
|
||||
// for variable types with updates that go the setValueFromUrl path in the update let's make sure their state is set to Done.
|
||||
await dispatch(upgradeLegacyQueries(toVariableIdentifier(variableInState)));
|
||||
dispatch(completeVariableLoading(identifier));
|
||||
}
|
||||
return Promise.resolve();
|
||||
@ -639,6 +639,7 @@ export const updateOptions = (identifier: VariableIdentifier, rethrow = false):
|
||||
const variableInState = getVariable(identifier.id, getState());
|
||||
try {
|
||||
dispatch(variableStateFetching(toVariablePayload(variableInState)));
|
||||
await dispatch(upgradeLegacyQueries(toVariableIdentifier(variableInState)));
|
||||
await variableAdapters.get(variableInState.type).updateOptions(variableInState);
|
||||
dispatch(completeVariableLoading(identifier));
|
||||
} catch (error) {
|
||||
@ -684,26 +685,31 @@ export function upgradeLegacyQueries(
|
||||
return;
|
||||
}
|
||||
|
||||
const datasource = await getDatasourceSrvFunc().get(variable.datasource ?? '');
|
||||
try {
|
||||
const datasource = await getDatasourceSrvFunc().get(variable.datasource ?? '');
|
||||
|
||||
if (hasLegacyVariableSupport(datasource)) {
|
||||
return;
|
||||
if (hasLegacyVariableSupport(datasource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasStandardVariableSupport(datasource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDataQueryType(variable.query)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
refId: `${datasource.name}-${identifier.id}-Variable-Query`,
|
||||
query: variable.query,
|
||||
};
|
||||
|
||||
dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: query })));
|
||||
} catch (err) {
|
||||
dispatch(notifyApp(createVariableErrorNotification('Failed to upgrade legacy queries', err)));
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (!hasStandardVariableSupport(datasource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDataQueryType(variable.query)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
refId: `${datasource.name}-${identifier.id}-Variable-Query`,
|
||||
query: variable.query,
|
||||
};
|
||||
|
||||
dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: query })));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -133,10 +133,11 @@ describe('when onTimeRangeUpdated is dispatched', () => {
|
||||
startRefreshMock,
|
||||
} = getTestContext();
|
||||
|
||||
const tester = await reduxTester<{ templating: TemplatingState }>({ preloadedState })
|
||||
const base = await reduxTester<{ templating: TemplatingState }>({ preloadedState })
|
||||
.givenRootReducer(getRootReducer())
|
||||
.whenActionIsDispatched(setOptionAsCurrent(toVariableIdentifier(interval), interval.options[0], false))
|
||||
.whenAsyncActionIsDispatched(onTimeRangeUpdated(range, dependencies), true);
|
||||
.whenAsyncActionIsDispatched(setOptionAsCurrent(toVariableIdentifier(interval), interval.options[0], false));
|
||||
|
||||
const tester = await base.whenAsyncActionIsDispatched(onTimeRangeUpdated(range, dependencies), true);
|
||||
|
||||
tester.thenDispatchedActionsShouldEqual(
|
||||
variableStateFetching(toVariablePayload({ type: 'interval', id: 'interval-0' })),
|
||||
|
Loading…
Reference in New Issue
Block a user