mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloud Monitoring: MQL support (#26551)
* cloud monitoring mql support * reduce nesting * remove resource type from deep link since. its removed for two reasons. first of all it is not needed for the link to work. secondly, by adding the resource type, the the link will differ from the query in grafana which I think is misleading * use frame.meta.executedQueryString instead of legacy meta Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { SelectableValue } from '@grafana/data';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { AnnotationsHelp, LabelFilter, Metrics, Project } from './';
|
||||
import { toOption } from '../functions';
|
||||
import { AnnotationTarget, MetricDescriptor } from '../types';
|
||||
import { AnnotationTarget, EditorMode, MetricDescriptor } from '../types';
|
||||
|
||||
const { Input } = LegacyForms;
|
||||
|
||||
@@ -25,6 +25,7 @@ interface State extends AnnotationTarget {
|
||||
}
|
||||
|
||||
const DefaultTarget: State = {
|
||||
editorMode: EditorMode.Visual,
|
||||
projectName: '',
|
||||
projects: [],
|
||||
metricType: '',
|
||||
@@ -42,7 +43,7 @@ const DefaultTarget: State = {
|
||||
export class AnnotationQueryEditor extends React.Component<Props, State> {
|
||||
state: State = DefaultTarget;
|
||||
|
||||
async UNSAFE_UNSAFE_componentWillMount() {
|
||||
async UNSAFE_componentWillMount() {
|
||||
// Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's
|
||||
// migration hook for this module.ts, we can do the migrations there instead.
|
||||
const { target, datasource } = this.props;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { TextArea } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
onChange: (query: string) => void;
|
||||
onRunQuery: () => void;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export function MQLQueryEditor({ query, onChange, onRunQuery }: React.PropsWithChildren<Props>) {
|
||||
const onKeyDown = (event: any) => {
|
||||
if (event.key === 'Enter' && (event.shiftKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
onRunQuery();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextArea
|
||||
name="Query"
|
||||
className="slate-query-field"
|
||||
value={query}
|
||||
rows={10}
|
||||
placeholder="Enter a Cloud Monitoring MQL query (Run with Shift+Enter)"
|
||||
onBlur={onRunQuery}
|
||||
onChange={e => onChange(e.currentTarget.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Project, Aggregations, Metrics, LabelFilter, GroupBys, Alignments, AlignmentPeriods, AliasBy } from '.';
|
||||
import { MetricQuery, MetricDescriptor } from '../types';
|
||||
import { Project, VisualMetricQueryEditor, AliasBy } from '.';
|
||||
import { MetricQuery, MetricDescriptor, EditorMode } from '../types';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { MQLQueryEditor } from './MQLQueryEditor';
|
||||
|
||||
export interface Props {
|
||||
refId: string;
|
||||
@@ -25,6 +26,7 @@ export const defaultState: State = {
|
||||
};
|
||||
|
||||
export const defaultQuery: (dataSource: CloudMonitoringDatasource) => MetricQuery = dataSource => ({
|
||||
editorMode: EditorMode.Visual,
|
||||
projectName: dataSource.getDefaultProject(),
|
||||
metricType: '',
|
||||
metricKind: '',
|
||||
@@ -36,13 +38,15 @@ export const defaultQuery: (dataSource: CloudMonitoringDatasource) => MetricQuer
|
||||
groupBys: [],
|
||||
filters: [],
|
||||
aliasBy: '',
|
||||
query: '',
|
||||
});
|
||||
|
||||
function Editor({
|
||||
refId,
|
||||
query,
|
||||
datasource,
|
||||
onChange,
|
||||
onChange: onQueryChange,
|
||||
onRunQuery,
|
||||
usedAlignmentPeriod,
|
||||
variableOptionGroup,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
@@ -56,6 +60,11 @@ function Editor({
|
||||
}
|
||||
}, [query.projectName, query.groupBys, query.metricType]);
|
||||
|
||||
const onChange = (metricQuery: MetricQuery) => {
|
||||
onQueryChange({ ...query, ...metricQuery });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onMetricTypeChange = async ({ valueType, metricKind, type, unit }: MetricDescriptor) => {
|
||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(
|
||||
{ valueType, metricKind, perSeriesAligner: state.perSeriesAligner },
|
||||
@@ -68,9 +77,6 @@ function Editor({
|
||||
onChange({ ...query, perSeriesAligner, metricType: type, unit, valueType, metricKind });
|
||||
};
|
||||
|
||||
const { labels } = state;
|
||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(query, datasource.templateSrv);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Project
|
||||
@@ -81,58 +87,33 @@ function Editor({
|
||||
onChange({ ...query, projectName });
|
||||
}}
|
||||
/>
|
||||
<Metrics
|
||||
templateSrv={datasource.templateSrv}
|
||||
projectName={query.projectName}
|
||||
metricType={query.metricType}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
datasource={datasource}
|
||||
onChange={onMetricTypeChange}
|
||||
>
|
||||
{metric => (
|
||||
<>
|
||||
<LabelFilter
|
||||
labels={labels}
|
||||
filters={query.filters!}
|
||||
onChange={filters => onChange({ ...query, filters })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<GroupBys
|
||||
groupBys={Object.keys(labels)}
|
||||
values={query.groupBys!}
|
||||
onChange={groupBys => onChange({ ...query, groupBys })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<Aggregations
|
||||
metricDescriptor={metric}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
crossSeriesReducer={query.crossSeriesReducer}
|
||||
groupBys={query.groupBys!}
|
||||
onChange={crossSeriesReducer => onChange({ ...query, crossSeriesReducer })}
|
||||
>
|
||||
{displayAdvancedOptions =>
|
||||
displayAdvancedOptions && (
|
||||
<Alignments
|
||||
alignOptions={alignOptions}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
perSeriesAligner={perSeriesAligner || ''}
|
||||
onChange={perSeriesAligner => onChange({ ...query, perSeriesAligner })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Aggregations>
|
||||
<AlignmentPeriods
|
||||
templateSrv={datasource.templateSrv}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
alignmentPeriod={query.alignmentPeriod || ''}
|
||||
perSeriesAligner={query.perSeriesAligner || ''}
|
||||
usedAlignmentPeriod={usedAlignmentPeriod}
|
||||
onChange={alignmentPeriod => onChange({ ...query, alignmentPeriod })}
|
||||
/>
|
||||
<AliasBy value={query.aliasBy || ''} onChange={aliasBy => onChange({ ...query, aliasBy })} />
|
||||
</>
|
||||
)}
|
||||
</Metrics>
|
||||
|
||||
{query.editorMode === EditorMode.Visual && (
|
||||
<VisualMetricQueryEditor
|
||||
labels={state.labels}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
usedAlignmentPeriod={usedAlignmentPeriod}
|
||||
onMetricTypeChange={onMetricTypeChange}
|
||||
onChange={onChange}
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
/>
|
||||
)}
|
||||
|
||||
{query.editorMode === EditorMode.MQL && (
|
||||
<MQLQueryEditor
|
||||
onChange={(q: string) => onQueryChange({ ...query, query: q })}
|
||||
onRunQuery={onRunQuery}
|
||||
query={query.query}
|
||||
></MQLQueryEditor>
|
||||
)}
|
||||
|
||||
<AliasBy
|
||||
value={query.aliasBy}
|
||||
onChange={aliasBy => {
|
||||
onChange({ ...query, aliasBy });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { Help, MetricQueryEditor, QueryTypeSelector, SLOQueryEditor } from './';
|
||||
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery } from '../types';
|
||||
import { ExploreQueryFieldProps, SelectableValue } from '@grafana/data';
|
||||
import { Segment } from '@grafana/ui';
|
||||
import { Help, MetricQueryEditor, SLOQueryEditor } from './';
|
||||
import { CloudMonitoringQuery, MetricQuery, QueryType, SLOQuery, queryTypes, EditorMode } from '../types';
|
||||
import { defaultQuery } from './MetricQueryEditor';
|
||||
import { defaultQuery as defaultSLOQuery } from './SLOQueryEditor';
|
||||
import { formatCloudMonitoringError, toOption } from '../functions';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
|
||||
export type Props = ExploreQueryFieldProps<CloudMonitoringDatasource, CloudMonitoringQuery>;
|
||||
|
||||
@@ -18,7 +19,7 @@ interface State {
|
||||
export class QueryEditor extends PureComponent<Props, State> {
|
||||
state: State = { lastQueryError: '' };
|
||||
|
||||
async UNSAFE_UNSAFE_componentWillMount() {
|
||||
async UNSAFE_componentWillMount() {
|
||||
const { datasource, query } = this.props;
|
||||
|
||||
// Unfortunately, migrations like this need to go UNSAFE_componentWillMount. As soon as there's
|
||||
@@ -76,21 +77,51 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryTypeSelector
|
||||
value={queryType}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
onChange={(queryType: QueryType) => {
|
||||
onChange({ ...query, sloQuery, queryType });
|
||||
onRunQuery();
|
||||
}}
|
||||
></QueryTypeSelector>
|
||||
<div className="gf-form-inline">
|
||||
<label className="gf-form-label query-keyword width-9">Query Type</label>
|
||||
<Segment
|
||||
value={[...queryTypes, ...variableOptionGroup.options].find(qt => qt.value === queryType)}
|
||||
options={[
|
||||
...queryTypes,
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: variableOptionGroup.options,
|
||||
},
|
||||
]}
|
||||
onChange={({ value }: SelectableValue<QueryType>) => {
|
||||
onChange({ ...query, sloQuery, queryType: value! });
|
||||
onRunQuery();
|
||||
}}
|
||||
/>
|
||||
|
||||
{query.queryType !== QueryType.SLO && (
|
||||
<button
|
||||
className="gf-form-label "
|
||||
onClick={() =>
|
||||
this.onQueryChange('metricQuery', {
|
||||
...metricQuery,
|
||||
editorMode: metricQuery.editorMode === EditorMode.MQL ? EditorMode.Visual : EditorMode.MQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span className="query-keyword">{'<>'}</span>
|
||||
{metricQuery.editorMode === EditorMode.MQL ? 'Switch to builder' : 'Edit MQL'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{queryType === QueryType.METRICS && (
|
||||
<MetricQueryEditor
|
||||
refId={query.refId}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
usedAlignmentPeriod={usedAlignmentPeriod}
|
||||
onChange={(query: MetricQuery) => this.onQueryChange('metricQuery', query)}
|
||||
onChange={(metricQuery: MetricQuery) => {
|
||||
this.props.onChange({ ...this.props.query, metricQuery });
|
||||
}}
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource}
|
||||
query={metricQuery}
|
||||
@@ -107,6 +138,7 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
query={sloQuery}
|
||||
></SLOQueryEditor>
|
||||
)}
|
||||
|
||||
<Help
|
||||
rawQuery={decodeURIComponent(meta?.executedQueryString ?? '')}
|
||||
lastQueryError={this.state.lastQueryError}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { Aggregations, Metrics, LabelFilter, GroupBys, Alignments, AlignmentPeriods } from '.';
|
||||
import { MetricQuery, MetricDescriptor } from '../types';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import CloudMonitoringDatasource from '../datasource';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
export interface Props {
|
||||
usedAlignmentPeriod?: number;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
onMetricTypeChange: (query: MetricDescriptor) => void;
|
||||
onChange: (query: MetricQuery) => void;
|
||||
query: MetricQuery;
|
||||
datasource: CloudMonitoringDatasource;
|
||||
labels: any;
|
||||
}
|
||||
|
||||
function Editor({
|
||||
query,
|
||||
labels,
|
||||
datasource,
|
||||
onChange,
|
||||
onMetricTypeChange,
|
||||
usedAlignmentPeriod,
|
||||
variableOptionGroup,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(query, datasource.templateSrv);
|
||||
|
||||
return (
|
||||
<Metrics
|
||||
templateSrv={datasource.templateSrv}
|
||||
projectName={query.projectName}
|
||||
metricType={query.metricType}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
datasource={datasource}
|
||||
onChange={onMetricTypeChange}
|
||||
>
|
||||
{metric => (
|
||||
<>
|
||||
<LabelFilter
|
||||
labels={labels}
|
||||
filters={query.filters!}
|
||||
onChange={filters => onChange({ ...query, filters })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<GroupBys
|
||||
groupBys={Object.keys(labels)}
|
||||
values={query.groupBys!}
|
||||
onChange={groupBys => onChange({ ...query, groupBys })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
<Aggregations
|
||||
metricDescriptor={metric}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
crossSeriesReducer={query.crossSeriesReducer}
|
||||
groupBys={query.groupBys!}
|
||||
onChange={crossSeriesReducer => onChange({ ...query, crossSeriesReducer })}
|
||||
>
|
||||
{displayAdvancedOptions =>
|
||||
displayAdvancedOptions && (
|
||||
<Alignments
|
||||
alignOptions={alignOptions}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
perSeriesAligner={perSeriesAligner || ''}
|
||||
onChange={perSeriesAligner => onChange({ ...query, perSeriesAligner })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Aggregations>
|
||||
<AlignmentPeriods
|
||||
templateSrv={datasource.templateSrv}
|
||||
templateVariableOptions={variableOptionGroup.options}
|
||||
alignmentPeriod={query.alignmentPeriod || ''}
|
||||
perSeriesAligner={query.perSeriesAligner || ''}
|
||||
usedAlignmentPeriod={usedAlignmentPeriod}
|
||||
onChange={alignmentPeriod => onChange({ ...query, alignmentPeriod })}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Metrics>
|
||||
);
|
||||
}
|
||||
|
||||
export const VisualMetricQueryEditor = React.memo(Editor);
|
||||
@@ -11,5 +11,7 @@ export { Aggregations } from './Aggregations';
|
||||
export { SimpleSelect } from './SimpleSelect';
|
||||
export { MetricQueryEditor } from './MetricQueryEditor';
|
||||
export { SLOQueryEditor } from './SLOQueryEditor';
|
||||
export { MQLQueryEditor } from './MQLQueryEditor';
|
||||
export { QueryTypeSelector } from './QueryType';
|
||||
export { QueryInlineField, QueryField } from './Fields';
|
||||
export { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
|
||||
import { CloudMonitoringOptions, CloudMonitoringQuery, Filter, MetricDescriptor, QueryType } from './types';
|
||||
import { CloudMonitoringOptions, CloudMonitoringQuery, Filter, MetricDescriptor, QueryType, EditorMode } from './types';
|
||||
import API from './api';
|
||||
import { DataSourceWithBackend } from '@grafana/runtime';
|
||||
import { CloudMonitoringVariableSupport } from './variables';
|
||||
@@ -114,6 +114,7 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
||||
filters: this.interpolateFilters(metricQuery.filters || [], scopedVars),
|
||||
groupBys: this.interpolateGroupBys(metricQuery.groupBys || [], scopedVars),
|
||||
view: metricQuery.view || 'FULL',
|
||||
editorMode: metricQuery.editorMode,
|
||||
},
|
||||
sloQuery: sloQuery && this.interpolateProps(sloQuery, scopedVars),
|
||||
};
|
||||
@@ -324,6 +325,10 @@ export default class CloudMonitoringDatasource extends DataSourceWithBackend<
|
||||
return !!selectorName && !!serviceId && !!sloId && !!projectName;
|
||||
}
|
||||
|
||||
if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
|
||||
return !!query.metricQuery.projectName && !!query.metricQuery.query;
|
||||
}
|
||||
|
||||
const { metricType } = query.metricQuery;
|
||||
|
||||
return !!metricType;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
{
|
||||
"path": "cloudmonitoring",
|
||||
"method": "GET",
|
||||
"url": "https://content-monitoring.googleapis.com",
|
||||
"url": "https://monitoring.googleapis.com",
|
||||
"jwtTokenAuth": {
|
||||
"scopes": ["https://www.googleapis.com/auth/monitoring.read"],
|
||||
"params": {
|
||||
|
||||
@@ -58,12 +58,18 @@ export enum QueryType {
|
||||
SLO = 'slo',
|
||||
}
|
||||
|
||||
export enum EditorMode {
|
||||
Visual = 'visual',
|
||||
MQL = 'mql',
|
||||
}
|
||||
|
||||
export const queryTypes = [
|
||||
{ label: 'Metrics', value: QueryType.METRICS },
|
||||
{ label: 'Service Level Objectives (SLO)', value: QueryType.SLO },
|
||||
];
|
||||
|
||||
export interface MetricQuery {
|
||||
editorMode: EditorMode;
|
||||
projectName: string;
|
||||
unit?: string;
|
||||
metricType: string;
|
||||
@@ -76,6 +82,7 @@ export interface MetricQuery {
|
||||
metricKind?: string;
|
||||
valueType?: string;
|
||||
view?: string;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export interface SLOQuery {
|
||||
|
||||
Reference in New Issue
Block a user