Variables: Adds new Api that allows proper QueryEditors for Query variables (#28217)

* Initial

* WIP

* wip

* Refactor: fixing types

* Refactor: Fixed more typings

* Feature: Moves TestData to new API

* Feature: Moves CloudMonitoringDatasource to new API

* Feature: Moves PrometheusDatasource to new Variables API

* Refactor: Clean up comments

* Refactor: changes to QueryEditorProps instead

* Refactor: cleans up testdata, prometheus and cloud monitoring variable support

* Refactor: adds variableQueryRunner

* Refactor: adds props to VariableQueryEditor

* Refactor: reverted Loki editor

* Refactor: refactor queryrunner into smaller pieces

* Refactor: adds upgrade query thunk

* Tests: Updates old tests

* Docs: fixes build errors for exported api

* Tests: adds guard tests

* Tests: adds QueryRunner tests

* Tests: fixes broken tests

* Tests: adds variableQueryObserver tests

* Test: adds tests for operator functions

* Test: adds VariableQueryRunner tests

* Refactor: renames dataSource

* Refactor: adds definition for standard variable support

* Refactor: adds cancellation to OptionPicker

* Refactor: changes according to Dominiks suggestion

* Refactor:tt

* Refactor: adds tests for factories

* Refactor: restructuring a bit

* Refactor: renames variableQueryRunner.ts

* Refactor: adds quick exit when runRequest returns errors

* Refactor: using TextArea from grafana/ui

* Refactor: changed from interfaces to classes instead

* Tests: fixes broken test

* Docs: fixes doc issue count

* Docs: fixes doc issue count

* Refactor: Adds check for self referencing queries

* Tests: fixed unused variable

* Refactor: Changes comments
This commit is contained in:
Hugo Häggmark
2020-11-18 15:10:32 +01:00
committed by GitHub
parent 5ae7280249
commit 112a755e18
48 changed files with 3251 additions and 311 deletions

View File

@@ -1,15 +1,15 @@
import React from 'react';
import { LegacyForms } from '@grafana/ui';
const { Input } = LegacyForms;
import { TemplateSrv } from '@grafana/runtime';
import { SelectableValue } from '@grafana/data';
import CloudMonitoringDatasource from '../datasource';
import { Metrics, LabelFilter, AnnotationsHelp, Project } from './';
import { AnnotationsHelp, LabelFilter, Metrics, Project } from './';
import { toOption } from '../functions';
import { AnnotationTarget, MetricDescriptor } from '../types';
const { Input } = LegacyForms;
export interface Props {
onQueryChange: (target: AnnotationTarget) => void;
target: AnnotationTarget;
@@ -52,7 +52,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
const variableOptionGroup = {
label: 'Template Variables',
options: datasource.variables.map(toOption),
options: datasource.getVariables().map(toOption),
};
const projects = await datasource.getProjects();

View File

@@ -1,11 +1,11 @@
import React, { PureComponent } from 'react';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
import { MetricQueryEditor, QueryTypeSelector, SLOQueryEditor, Help } from './';
import { Help, MetricQueryEditor, QueryTypeSelector, SLOQueryEditor } from './';
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery } from '../types';
import { defaultQuery } from './MetricQueryEditor';
import { defaultQuery as defaultSLOQuery } from './SLOQueryEditor';
import { toOption, formatCloudMonitoringError } from '../functions';
import { formatCloudMonitoringError, toOption } from '../functions';
import CloudMonitoringDatasource from '../datasource';
import { ExploreQueryFieldProps } from '@grafana/data';
@@ -71,7 +71,7 @@ export class QueryEditor extends PureComponent<Props, State> {
const variableOptionGroup = {
label: 'Template Variables',
expanded: false,
options: datasource.variables.map(toOption),
options: datasource.getVariables().map(toOption),
};
return (

View File

@@ -1,42 +1,38 @@
import React from 'react';
// @ts-ignore
import renderer from 'react-test-renderer';
import { CloudMonitoringVariableQueryEditor } from './VariableQueryEditor';
import { VariableQueryProps } from 'app/types/plugins';
import { MetricFindQueryTypes } from '../types';
import { VariableModel } from 'app/features/variables/types';
import { CloudMonitoringVariableQueryEditor, Props } from './VariableQueryEditor';
import { CloudMonitoringVariableQuery, MetricFindQueryTypes } from '../types';
import CloudMonitoringDatasource from '../datasource';
import { VariableModel } from '@grafana/data';
jest.mock('../functions', () => ({
getMetricTypes: (): any => ({ metricTypes: [], selectedMetricType: '' }),
extractServicesFromMetricDescriptors: (): any[] => [],
}));
jest.mock('../../../../core/config', () => {
console.warn('[This test uses old variable system, needs a rewrite]');
const original = jest.requireActual('../../../../core/config');
const config = original.getConfig();
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
getConfig: () => ({
...config,
featureToggles: {
...config.featureToggles,
newVariables: false,
},
...original,
getTemplateSrv: () => ({
replace: (s: string) => s,
getVariables: () => ([] as unknown) as VariableModel[],
}),
};
});
const props: VariableQueryProps = {
onChange: (query, definition) => {},
query: {},
datasource: {
const props: Props = {
onChange: query => {},
query: ({} as unknown) as CloudMonitoringVariableQuery,
datasource: ({
getDefaultProject: () => '',
getProjects: async () => Promise.resolve([]),
getMetricTypes: async (projectName: string) => Promise.resolve([]),
getSLOServices: async (projectName: string, serviceId: string) => Promise.resolve([]),
getSLOServices: async (projectName: string) => Promise.resolve([]),
getServiceLevelObjectives: (projectName: string, serviceId: string) => Promise.resolve([]),
},
templateSrv: { replace: (s: string) => s, getVariables: () => ([] as unknown) as VariableModel[] },
} as unknown) as CloudMonitoringDatasource,
onRunQuery: () => {},
};
describe('VariableQueryEditor', () => {
@@ -46,10 +42,9 @@ describe('VariableQueryEditor', () => {
});
describe('and a new variable is created', () => {
// these test need to be updated to reflect the changes from old variables system to new
it('should trigger a query using the first query type in the array', done => {
props.onChange = (query, definition) => {
expect(definition).toBe('Google Cloud Monitoring - Projects');
props.onChange = query => {
expect(query.selectedQueryType).toBe('projects');
done();
};
renderer.create(<CloudMonitoringVariableQueryEditor {...props} />).toJSON();
@@ -57,11 +52,10 @@ describe('VariableQueryEditor', () => {
});
describe('and an existing variable is edited', () => {
// these test need to be updated to reflect the changes from old variables system to new
it('should trigger new query using the saved query type', done => {
props.query = { selectedQueryType: MetricFindQueryTypes.LabelKeys };
props.onChange = (query, definition) => {
expect(definition).toBe('Google Cloud Monitoring - Label Keys');
props.query = ({ selectedQueryType: MetricFindQueryTypes.LabelKeys } as unknown) as CloudMonitoringVariableQuery;
props.onChange = query => {
expect(query.selectedQueryType).toBe('labelKeys');
done();
};
renderer.create(<CloudMonitoringVariableQueryEditor {...props} />).toJSON();

View File

@@ -1,10 +1,26 @@
import React, { PureComponent } from 'react';
import { VariableQueryProps } from 'app/types/plugins';
import { SimpleSelect } from './';
import { extractServicesFromMetricDescriptors, getLabelKeys, getMetricTypes } from '../functions';
import { MetricFindQueryTypes, VariableQueryData } from '../types';
import {
CloudMonitoringOptions,
CloudMonitoringQuery,
CloudMonitoringVariableQuery,
MetricDescriptor,
MetricFindQueryTypes,
VariableQueryData,
} from '../types';
import CloudMonitoringDatasource from '../datasource';
import { getTemplateSrv } from '@grafana/runtime';
import { QueryEditorProps } from '@grafana/data';
export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQueryProps, VariableQueryData> {
export type Props = QueryEditorProps<
CloudMonitoringDatasource,
CloudMonitoringQuery,
CloudMonitoringOptions,
CloudMonitoringVariableQuery
>;
export class CloudMonitoringVariableQueryEditor extends PureComponent<Props, VariableQueryData> {
queryTypes: Array<{ value: string; name: string }> = [
{ value: MetricFindQueryTypes.Projects, name: 'Projects' },
{ value: MetricFindQueryTypes.Services, name: 'Services' },
@@ -36,7 +52,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
loading: true,
};
constructor(props: VariableQueryProps) {
constructor(props: Props) {
super(props);
this.state = Object.assign(
this.defaults,
@@ -46,7 +62,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
}
async componentDidMount() {
const projects = await this.props.datasource.getProjects();
const projects = (await this.props.datasource.getProjects()) as MetricDescriptor[];
const metricDescriptors = await this.props.datasource.getMetricTypes(
this.props.query.projectName || this.props.datasource.getDefaultProject()
);
@@ -56,7 +72,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
}));
let selectedService = '';
if (services.some(s => s.value === this.props.templateSrv.replace(this.state.selectedService))) {
if (services.some(s => s.value === getTemplateSrv().replace(this.state.selectedService))) {
selectedService = this.state.selectedService;
} else if (services && services.length > 0) {
selectedService = services[0].value;
@@ -65,8 +81,8 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
const { metricTypes, selectedMetricType } = getMetricTypes(
metricDescriptors,
this.state.selectedMetricType,
this.props.templateSrv.replace(this.state.selectedMetricType),
this.props.templateSrv.replace(selectedService)
getTemplateSrv().replace(this.state.selectedMetricType),
getTemplateSrv().replace(selectedService)
);
const sloServices = await this.props.datasource.getSLOServices(this.state.projectName);
@@ -87,8 +103,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
onPropsChange = () => {
const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state;
const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType)!;
this.props.onChange(queryModel, `Google Cloud Monitoring - ${query.name}`);
this.props.onChange({ ...queryModel, refId: 'CloudMonitoringVariableQueryEditor-VariableQuery' });
};
async onQueryTypeChange(queryType: string) {
@@ -106,8 +121,8 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
const { metricTypes, selectedMetricType } = getMetricTypes(
metricDescriptors,
this.state.selectedMetricType,
this.props.templateSrv.replace(this.state.selectedMetricType),
this.props.templateSrv.replace(this.state.selectedService)
getTemplateSrv().replace(this.state.selectedMetricType),
getTemplateSrv().replace(this.state.selectedService)
);
const sloServices = await this.props.datasource.getSLOServices(projectName);
@@ -126,8 +141,8 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
const { metricTypes, selectedMetricType } = getMetricTypes(
this.state.metricDescriptors,
this.state.selectedMetricType,
this.props.templateSrv.replace(this.state.selectedMetricType),
this.props.templateSrv.replace(service)
getTemplateSrv().replace(this.state.selectedMetricType),
getTemplateSrv().replace(service)
);
const state: any = {
selectedService: service,
@@ -150,7 +165,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
this.setState({ labelKey }, () => this.onPropsChange());
}
componentDidUpdate(prevProps: Readonly<VariableQueryProps>, prevState: Readonly<VariableQueryData>) {
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<VariableQueryData>) {
const selecQueryTypeChanged = prevState.selectedQueryType !== this.state.selectedQueryType;
const selectSLOServiceChanged = this.state.selectedSLOService !== prevState.selectedSLOService;
if (selecQueryTypeChanged || selectSLOServiceChanged) {
@@ -162,7 +177,7 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
let result = { labels: this.state.labels, labelKey: this.state.labelKey };
if (selectedMetricType && selectedQueryType === MetricFindQueryTypes.LabelValues) {
const labels = await getLabelKeys(this.props.datasource, selectedMetricType, projectName);
const labelKey = labels.some(l => l === this.props.templateSrv.replace(this.state.labelKey))
const labelKey = labels.some(l => l === getTemplateSrv().replace(this.state.labelKey))
? this.state.labelKey
: labels[0];
result = { labels, labelKey };
@@ -171,10 +186,12 @@ export class CloudMonitoringVariableQueryEditor extends PureComponent<VariableQu
}
insertTemplateVariables(options: any) {
const templateVariables = this.props.templateSrv.getVariables().map((v: any) => ({
name: `$${v.name}`,
value: `$${v.name}`,
}));
const templateVariables = getTemplateSrv()
.getVariables()
.map((v: any) => ({
name: `$${v.name}`,
value: `$${v.name}`,
}));
return [...templateVariables, ...options];
}