Variables: Explicitly type variable editor extended state (#44749)

* Narrow Variable editor state using selector functions

 - Explicitly type "extended" editor state in editor/reducter.ts using a union
 - Create selectors to narrow the types, using unique properties from each extended state to discriminate the union
 - Update DataSourceVariableEditor to use new style of redux connector
 - Update variable editor components to use new selectors

* fix tests

* Make adhoc variable infoText optional, because it is!

* Add AdHocVariableEditor tests

* DataSourceVariableEditor tests

* comment

* reset

* Wrote tests for selectors \(that actually caught a bug, whodathunkit)

* fix stray types and lint issues

* Rename selector functions
pull/45033/head
Josh Hunt 3 years ago committed by GitHub
parent ed7b9905c2
commit 1c6eca09bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx
  2. 12
      public/app/features/variables/adhoc/AdHocVariableEditor.tsx
  3. 5
      public/app/features/variables/adhoc/reducer.ts
  4. 55
      public/app/features/variables/datasource/DataSourceVariableEditor.test.tsx
  5. 63
      public/app/features/variables/datasource/DataSourceVariableEditor.tsx
  6. 4
      public/app/features/variables/datasource/reducer.ts
  7. 3
      public/app/features/variables/editor/reducer.test.ts
  8. 24
      public/app/features/variables/editor/reducer.ts
  9. 89
      public/app/features/variables/editor/selectors.test.ts
  10. 41
      public/app/features/variables/editor/selectors.ts
  11. 13
      public/app/features/variables/query/QueryVariableEditor.test.tsx
  12. 14
      public/app/features/variables/query/QueryVariableEditor.tsx
  13. 13
      public/app/features/variables/query/actions.ts
  14. 16
      public/app/features/variables/query/reducer.ts

@ -0,0 +1,53 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor';
import { initialAdHocVariableModelState } from './reducer';
import { selectOptionInTest } from '@grafana/ui';
import { getSelectParent } from '@grafana/ui/src/components/Select/test-utils';
const props = {
extended: {
dataSources: [
{ text: 'Prometheus', value: null }, // default datasource
{ text: 'Loki', value: { type: 'loki-ds', uid: 'abc' } },
],
},
variable: { ...initialAdHocVariableModelState },
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} />);
const selectContainer = getSelectParent(screen.getByLabelText('Data source'));
expect(selectContainer).toHaveTextContent('Prometheus');
});
it('calls the callback when changing the datasource', async () => {
render(<AdHocVariableEditor {...props} />);
await selectOptionInTest(screen.getByLabelText('Data source'), 'Loki');
expect(props.changeVariableDatasource).toBeCalledWith({ type: 'loki-ds', uid: 'abc' });
});
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();
});
});

@ -5,15 +5,14 @@ import { DataSourceRef, SelectableValue } from '@grafana/data';
import { AdHocVariableModel } from '../types';
import { VariableEditorProps } from '../editor/types';
import { VariableEditorState } from '../editor/reducer';
import { AdHocVariableEditorState } from './reducer';
import { changeVariableDatasource, initAdHocVariableEditor } from './actions';
import { StoreState } from 'app/types';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableSelectField } from '../editor/VariableSelectField';
import { getAdhocVariableEditorState } from '../editor/selectors';
const mapStateToProps = (state: StoreState) => ({
editor: state.templating.editor as VariableEditorState<AdHocVariableEditorState>,
extended: getAdhocVariableEditorState(state.templating.editor),
});
const mapDispatchToProps = {
@ -37,9 +36,9 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
};
render() {
const { variable, editor } = this.props;
const dataSources = editor.extended?.dataSources ?? [];
const infoText = editor.extended?.infoText ?? null;
const { variable, extended } = this.props;
const dataSources = extended?.dataSources ?? [];
const infoText = extended?.infoText ?? null;
const options = dataSources.map((ds) => ({ label: ds.text, value: ds.value }));
const value = options.find((o) => o.value?.uid === variable.datasource?.uid) ?? options[0];
@ -56,6 +55,7 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
labelWidth={10}
/>
</InlineFieldRow>
{infoText ? <Alert title={infoText} severity="info" /> : null}
</VerticalGroup>
</VerticalGroup>

@ -1,5 +1,4 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { DataSourceRef } from '@grafana/data';
import { AdHocVariableFilter, AdHocVariableModel, initialVariableModelState } from 'app/features/variables/types';
import { getInstanceState, initialVariablesState, VariablePayload, VariablesState } from '../state/types';
@ -8,10 +7,6 @@ export interface AdHocVariabelFilterUpdate {
index: number;
filter: AdHocVariableFilter;
}
export interface AdHocVariableEditorState {
infoText: string;
dataSources: Array<{ text: string; value: DataSourceRef | null }>;
}
export const initialAdHocVariableModelState: AdHocVariableModel = {
...initialVariableModelState,

@ -0,0 +1,55 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { DataSourceVariableEditorUnConnected as DataSourceVariableEditor } from './DataSourceVariableEditor';
import { initialDataSourceVariableModelState } from './reducer';
import { selectOptionInTest } from '@grafana/ui';
import { getSelectParent } from '@grafana/ui/src/components/Select/test-utils';
const props = {
extended: {
dataSourceTypes: [
{ text: 'Prometheus', value: 'ds-prom' },
{ text: 'Loki', value: 'ds-loki' },
],
},
variable: { ...initialDataSourceVariableModelState },
onPropChange: jest.fn(),
// connected actions
initDataSourceVariableEditor: jest.fn(),
changeVariableMultiValue: jest.fn(),
};
describe('DataSourceVariableEditor', () => {
beforeEach(() => {
props.onPropChange.mockReset();
});
it('has a data source select menu', () => {
render(<DataSourceVariableEditor {...props} />);
const selectContainer = getSelectParent(screen.getByLabelText('Type'));
expect(selectContainer).toHaveTextContent('Prometheus');
});
it('calls the handler when the data source is changed', async () => {
render(<DataSourceVariableEditor {...props} />);
await selectOptionInTest(screen.getByLabelText('Type'), 'Loki');
expect(props.onPropChange).toBeCalledWith({ propName: 'query', propValue: 'ds-loki', updateOptions: true });
});
it('has a regex filter field', () => {
render(<DataSourceVariableEditor {...props} />);
const field = screen.getByLabelText('Instance name filter');
expect(field).toBeInTheDocument();
});
it('calls the handler when the regex filter is changed', () => {
render(<DataSourceVariableEditor {...props} />);
const field = screen.getByLabelText('Instance name filter');
fireEvent.change(field, { target: { value: '/prod/' } });
expect(props.onPropChange).toBeCalledWith({ propName: 'regex', propValue: '/prod/' });
});
});

@ -1,34 +1,34 @@
import React, { FormEvent, PureComponent } from 'react';
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
import { connect, ConnectedProps } from 'react-redux';
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
import { DataSourceVariableModel, VariableWithMultiSupport } from '../types';
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { VariableEditorState } from '../editor/reducer';
import { DataSourceVariableEditorState } from './reducer';
import { initDataSourceVariableEditor } from './actions';
import { StoreState } from '../../../types';
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
import { changeVariableMultiValue } from '../state/actions';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableSelectField } from '../editor/VariableSelectField';
import { SelectableValue } from '@grafana/data';
import { VariableTextField } from '../editor/VariableTextField';
import { selectors } from '@grafana/e2e-selectors';
import { getDatasourceVariableEditorState } from '../editor/selectors';
export interface OwnProps extends VariableEditorProps<DataSourceVariableModel> {}
const mapStateToProps = (state: StoreState) => ({
extended: getDatasourceVariableEditorState(state.templating.editor),
});
interface ConnectedProps {
editor: VariableEditorState<DataSourceVariableEditorState>;
}
const mapDispatchToProps = {
initDataSourceVariableEditor,
changeVariableMultiValue,
};
interface DispatchProps {
initDataSourceVariableEditor: typeof initDataSourceVariableEditor;
changeVariableMultiValue: typeof changeVariableMultiValue;
}
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = OwnProps & ConnectedProps & DispatchProps;
export interface OwnProps extends VariableEditorProps<DataSourceVariableModel> {}
type Props = OwnProps & ConnectedProps<typeof connector>;
export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
componentDidMount() {
@ -55,11 +55,14 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
};
getSelectedDataSourceTypeValue = (): string => {
if (!this.props.editor.extended?.dataSourceTypes?.length) {
const { extended } = this.props;
if (!extended?.dataSourceTypes.length) {
return '';
}
const foundItem = this.props.editor.extended?.dataSourceTypes.find((ds) => ds.value === this.props.variable.query);
const value = foundItem ? foundItem.value : this.props.editor.extended?.dataSourceTypes[0].value;
const foundItem = extended.dataSourceTypes.find((ds) => ds.value === this.props.variable.query);
const value = foundItem ? foundItem.value : extended.dataSourceTypes[0].value;
return value ?? '';
};
@ -68,10 +71,13 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
};
render() {
const typeOptions = this.props.editor.extended?.dataSourceTypes?.length
? this.props.editor.extended?.dataSourceTypes?.map((ds) => ({ value: ds.value ?? '', label: ds.text }))
const { variable, extended, changeVariableMultiValue } = this.props;
const typeOptions = extended?.dataSourceTypes?.length
? extended.dataSourceTypes?.map((ds) => ({ value: ds.value ?? '', label: ds.text }))
: [];
const typeValue = typeOptions.find((o) => o.value === this.props.variable.query) ?? typeOptions[0];
const typeValue = typeOptions.find((o) => o.value === variable.query) ?? typeOptions[0];
return (
<VerticalGroup spacing="xs">
@ -110,9 +116,9 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
</VerticalGroup>
<SelectionOptionsEditor
variable={this.props.variable}
variable={variable}
onPropChange={this.onSelectionOptionsChange}
onMultiChanged={this.props.changeVariableMultiValue}
onMultiChanged={changeVariableMultiValue}
/>
</VerticalGroup>
</VerticalGroup>
@ -120,17 +126,4 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
}
}
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (state, ownProps) => ({
editor: state.templating.editor as VariableEditorState<DataSourceVariableEditorState>,
});
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
initDataSourceVariableEditor,
changeVariableMultiValue,
};
export const DataSourceVariableEditor = connectWithStore(
DataSourceVariableEditorUnConnected,
mapStateToProps,
mapDispatchToProps
);
export const DataSourceVariableEditor = connector(DataSourceVariableEditorUnConnected);

@ -5,10 +5,6 @@ import { DataSourceVariableModel, initialVariableModelState, VariableOption, Var
import { getInstanceState, initialVariablesState, VariablePayload, VariablesState } from '../state/types';
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants';
export interface DataSourceVariableEditorState {
dataSourceTypes: Array<{ text: string; value: string }>;
}
export const initialDataSourceVariableModelState: DataSourceVariableModel = {
...initialVariableModelState,
type: 'datasource',

@ -62,7 +62,7 @@ describe('variableEditorReducer', () => {
name: 'A name',
isValid: false,
errors: { update: 'Something wrong' },
extended: { prop: 1000 },
extended: null,
};
const payload = toVariablePayload({ id: '0', type: 'textbox' });
reducerTester<VariableEditorState>()
@ -188,6 +188,7 @@ describe('variableEditorReducer', () => {
.thenStateShouldEqual({
...initialVariableEditorState,
extended: {
// @ts-ignore - temp ignoring this, we'll fix it soon
someProp: [{}],
},
});

@ -1,13 +1,30 @@
import { DataSourceApi, DataSourceRef } from '@grafana/data';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { VariablePayload } from '../state/types';
import { VariableQueryEditorType } from '../types';
type VariableEditorExtension<ExtendedProps extends {} = {}> = { [P in keyof ExtendedProps]: ExtendedProps[P] };
export interface VariableEditorState<ExtendedProps extends {} = {}> {
export interface AdHocVariableEditorState {
infoText?: string;
dataSources: Array<{ text: string; value: DataSourceRef | null }>;
}
export interface DataSourceVariableEditorState {
dataSourceTypes: Array<{ text: string; value: string }>;
}
export interface QueryVariableEditorState {
VariableQueryEditor: VariableQueryEditorType;
dataSource: DataSourceApi | null;
}
type VariableEditorExtension = AdHocVariableEditorState | DataSourceVariableEditorState | QueryVariableEditorState;
export interface VariableEditorState {
id: string;
name: string;
errors: Record<string, string>;
isValid: boolean;
extended: VariableEditorExtension<ExtendedProps> | null;
extended: VariableEditorExtension | null;
}
export const initialVariableEditorState: VariableEditorState = {
@ -65,6 +82,7 @@ const variableEditorReducerSlice = createSlice({
state: VariableEditorState,
action: PayloadAction<{ propName: string; propValue: any }>
) => {
// @ts-ignore - temp ignoring the errors now the state type is more strict
state.extended = {
...state.extended,
[action.payload.propName]: action.payload.propValue,

@ -0,0 +1,89 @@
import {
getAdhocVariableEditorState,
getDatasourceVariableEditorState,
getQueryVariableEditorState,
} from './selectors';
import {
AdHocVariableEditorState,
DataSourceVariableEditorState,
initialVariableEditorState,
QueryVariableEditorState,
} from './reducer';
import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor';
import { DataSourceApi } from '@grafana/data';
const adhocExtended: AdHocVariableEditorState = {
dataSources: [
{ text: 'Prometheus', value: null }, // default datasource
{ text: 'Loki', value: { type: 'loki-ds', uid: 'abc' } },
],
};
const datasourceExtended: DataSourceVariableEditorState = {
dataSourceTypes: [
{ text: 'Prometheus', value: 'ds-prom' },
{ text: 'Loki', value: 'ds-loki' },
],
};
const queryExtended: QueryVariableEditorState = {
VariableQueryEditor: LegacyVariableQueryEditor,
dataSource: {} as unknown as DataSourceApi,
};
const adhocVariableState = {
...initialVariableEditorState,
extended: adhocExtended,
};
const datasourceVariableState = {
...initialVariableEditorState,
extended: datasourceExtended,
};
const queryVariableState = {
...initialVariableEditorState,
extended: queryExtended,
};
describe('getAdhocVariableEditorState', () => {
it('returns the extended properties for adhoc variable state', () => {
expect(getAdhocVariableEditorState(adhocVariableState)).toBe(adhocExtended);
});
it('returns null for datasource variable state', () => {
expect(getAdhocVariableEditorState(datasourceVariableState)).toBeNull();
});
it('returns null for query variable state', () => {
expect(getAdhocVariableEditorState(queryVariableState)).toBeNull();
});
});
describe('getDatasourceVariableEditorState', () => {
it('returns the extended properties for datasource variable state', () => {
expect(getDatasourceVariableEditorState(datasourceVariableState)).toBe(datasourceExtended);
});
it('returns null for adhoc variable state', () => {
expect(getDatasourceVariableEditorState(adhocVariableState)).toBeNull();
});
it('returns null for query variable state', () => {
expect(getDatasourceVariableEditorState(queryVariableState)).toBeNull();
});
});
describe('getQueryVariableEditorState', () => {
it('returns the extended properties for query variable state', () => {
expect(getQueryVariableEditorState(queryVariableState)).toBe(queryExtended);
});
it('returns null for adhoc variable state', () => {
expect(getQueryVariableEditorState(adhocVariableState)).toBeNull();
});
it('returns null for datasource variable state', () => {
expect(getQueryVariableEditorState(datasourceVariableState)).toBeNull();
});
});

@ -0,0 +1,41 @@
import {
AdHocVariableEditorState,
DataSourceVariableEditorState,
QueryVariableEditorState,
VariableEditorState,
} from './reducer';
/**
* Narrows generic variable editor state down to specific Adhoc variable extended editor state
*/
export function getAdhocVariableEditorState(editorState: VariableEditorState): AdHocVariableEditorState | null {
if (editorState.extended && 'dataSources' in editorState.extended) {
return editorState.extended;
}
return null;
}
/**
* Narrows generic variable editor state down to specific Datasource variable extended editor state
*/
export function getDatasourceVariableEditorState(
editorState: VariableEditorState
): DataSourceVariableEditorState | null {
if (editorState.extended && 'dataSourceTypes' in editorState.extended) {
return editorState.extended;
}
return null;
}
/**
* Narrows generic variable editor state down to specific Query variable extended editor state
*/
export function getQueryVariableEditorState(editorState: VariableEditorState): QueryVariableEditorState | null {
if (editorState.extended && 'dataSource' in editorState.extended) {
return editorState.extended;
}
return null;
}

@ -5,7 +5,6 @@ import { DataSourceApi } from '@grafana/data';
import { Props, QueryVariableEditorUnConnected } from './QueryVariableEditor';
import { initialQueryVariableModelState } from './reducer';
import { initialVariableEditorState } from '../editor/reducer';
import { describe, expect } from '../../../../test/lib/common';
import { LegacyVariableQueryEditor } from '../editor/LegacyVariableQueryEditor';
import { mockDataSource } from 'app/features/alerting/unified/mocks';
@ -13,19 +12,17 @@ import { DataSourceType } from 'app/features/alerting/unified/utils/datasource';
import { NEW_VARIABLE_ID } from '../constants';
const setupTestContext = (options: Partial<Props>) => {
const extended = {
VariableQueryEditor: LegacyVariableQueryEditor,
dataSource: {} as unknown as DataSourceApi,
};
const defaults: Props = {
variable: { ...initialQueryVariableModelState },
initQueryVariableEditor: jest.fn(),
changeQueryVariableDataSource: jest.fn(),
changeQueryVariableQuery: jest.fn(),
changeVariableMultiValue: jest.fn(),
editor: {
...initialVariableEditorState,
extended: {
VariableQueryEditor: LegacyVariableQueryEditor,
dataSource: {} as unknown as DataSourceApi,
},
},
extended,
onPropChange: jest.fn(),
};

@ -8,9 +8,7 @@ import { DataSourceInstanceSettings, getDataSourceRef, LoadingState, SelectableV
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types';
import { QueryVariableEditorState } from './reducer';
import { changeQueryVariableDataSource, changeQueryVariableQuery, initQueryVariableEditor } from './actions';
import { VariableEditorState } from '../editor/reducer';
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
import { StoreState } from '../../../types';
import { toVariableIdentifier } from '../state/types';
@ -21,9 +19,10 @@ import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableTextField } from '../editor/VariableTextField';
import { QueryVariableRefreshSelect } from './QueryVariableRefreshSelect';
import { QueryVariableSortSelect } from './QueryVariableSortSelect';
import { getQueryVariableEditorState } from '../editor/selectors';
const mapStateToProps = (state: StoreState) => ({
editor: state.templating.editor as VariableEditorState<QueryVariableEditorState>,
extended: getQueryVariableEditorState(state.templating.editor),
});
const mapDispatchToProps = {
@ -114,14 +113,15 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
};
renderQueryEditor = () => {
const { editor, variable } = this.props;
if (!editor.extended || !editor.extended.dataSource || !editor.extended.VariableQueryEditor) {
const { extended, variable } = this.props;
if (!extended || !extended.dataSource || !extended.VariableQueryEditor) {
return null;
}
const query = variable.query;
const datasource = editor.extended.dataSource;
const VariableQueryEditor = editor.extended.VariableQueryEditor;
const datasource = extended.dataSource;
const VariableQueryEditor = extended.VariableQueryEditor;
if (isLegacyQueryEditor(VariableQueryEditor, datasource)) {
return (

@ -6,19 +6,14 @@ import { updateOptions } from '../state/actions';
import { QueryVariableModel } from '../types';
import { ThunkResult } from '../../../types';
import { getVariable } from '../state/selectors';
import {
addVariableEditorError,
changeVariableEditorExtended,
removeVariableEditorError,
VariableEditorState,
} from '../editor/reducer';
import { addVariableEditorError, changeVariableEditorExtended, removeVariableEditorError } from '../editor/reducer';
import { changeVariableProp } from '../state/sharedReducer';
import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types';
import { getVariableQueryEditor } from '../editor/getVariableQueryEditor';
import { getVariableQueryRunner } from './VariableQueryRunner';
import { variableQueryObserver } from './variableQueryObserver';
import { QueryVariableEditorState } from './reducer';
import { hasOngoingTransaction } from '../utils';
import { getQueryVariableEditorState } from '../editor/selectors';
export const updateQueryVariableOptions = (
identifier: VariableIdentifier,
@ -71,8 +66,8 @@ export const changeQueryVariableDataSource = (
): ThunkResult<void> => {
return async (dispatch, getState) => {
try {
const editorState = getState().templating.editor as VariableEditorState<QueryVariableEditorState>;
const previousDatasource = editorState.extended?.dataSource;
const extendedEditorState = getQueryVariableEditorState(getState().templating.editor);
const previousDatasource = extendedEditorState?.dataSource;
const dataSource = await getDataSourceSrv().get(name ?? '');
if (previousDatasource && previousDatasource.type !== dataSource?.type) {
dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: '' })));

@ -1,15 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { isNumber, sortBy, toLower, uniqBy } from 'lodash';
import { DataSourceApi, MetricFindValue, stringToJsRegex } from '@grafana/data';
import { MetricFindValue, stringToJsRegex } from '@grafana/data';
import {
initialVariableModelState,
QueryVariableModel,
VariableOption,
VariableQueryEditorType,
VariableRefresh,
VariableSort,
} from '../types';
import { initialVariableModelState, QueryVariableModel, VariableOption, VariableRefresh, VariableSort } from '../types';
import { getInstanceState, initialVariablesState, VariablePayload, VariablesState } from '../state/types';
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, NONE_VARIABLE_TEXT, NONE_VARIABLE_VALUE } from '../constants';
@ -19,11 +12,6 @@ interface VariableOptionsUpdate {
results: MetricFindValue[];
}
export interface QueryVariableEditorState {
VariableQueryEditor: VariableQueryEditorType;
dataSource: DataSourceApi | null;
}
export const initialQueryVariableModelState: QueryVariableModel = {
...initialVariableModelState,
type: 'query',

Loading…
Cancel
Save