CloudMonitoring: Allow to set a custom value or disable graph_period (#48646)

This commit is contained in:
Andres Martinez Gotor
2022-05-09 13:43:10 +02:00
committed by GitHub
parent 6923b4c6c6
commit b1bde7667f
13 changed files with 186 additions and 37 deletions

View File

@@ -338,6 +338,7 @@ func (s *Service) buildQueryExecutors(req *backend.QueryDataRequest) ([]cloudMon
IntervalMS: query.Interval.Milliseconds(),
AliasBy: q.MetricQuery.AliasBy,
timeRange: req.Queries[0].TimeRange,
GraphPeriod: q.MetricQuery.GraphPeriod,
}
} else {
cmtsf.AliasBy = q.MetricQuery.AliasBy

View File

@@ -20,6 +20,21 @@ import (
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
)
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *backend.QueryDataRequest) string {
// GraphPeriod needs to be explicitly disabled.
// If not set, the default behavior is to set an automatic value
if timeSeriesQuery.GraphPeriod != "disabled" {
graphPeriod := timeSeriesQuery.GraphPeriod
if graphPeriod == "auto" || graphPeriod == "" {
intervalCalculator := intervalv2.NewCalculator(intervalv2.CalculatorOptions{})
interval := intervalCalculator.Calculate(req.Queries[0].TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, req.Queries[0].MaxDataPoints)
graphPeriod = interval.Text
}
return fmt.Sprintf(" | graph_period %s", graphPeriod)
}
return ""
}
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, req *backend.QueryDataRequest,
s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, cloudMonitoringResponse, string, error) {
dr := &backend.DataResponse{}
@@ -35,13 +50,11 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, r
slog.Info("No project name set on query, using project name from datasource", "projectName", projectName)
}
intervalCalculator := intervalv2.NewCalculator(intervalv2.CalculatorOptions{})
interval := intervalCalculator.Calculate(req.Queries[0].TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, req.Queries[0].MaxDataPoints)
timeSeriesQuery.Query += timeSeriesQuery.appendGraphPeriod(req)
from := req.Queries[0].TimeRange.From
to := req.Queries[0].TimeRange.To
timeFormat := "2006/01/02-15:04:05"
timeSeriesQuery.Query += fmt.Sprintf(" | graph_period %s | within d'%s', d'%s'", interval.Text, from.UTC().Format(timeFormat), to.UTC().Format(timeFormat))
timeSeriesQuery.Query += fmt.Sprintf(" | within d'%s', d'%s'", from.UTC().Format(timeFormat), to.UTC().Format(timeFormat))
buf, err := json.Marshal(map[string]interface{}{
"query": timeSeriesQuery.Query,

View File

@@ -101,4 +101,14 @@ func TestTimeSeriesQuery(t *testing.T) {
require.True(t, ok)
assert.Equal(t, "6724404429462225363", labels["resource.label.instance_id"])
})
t.Run("appends graph_period to the query", func(t *testing.T) {
query := &cloudMonitoringTimeSeriesQuery{}
assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), " | graph_period 10ms")
})
t.Run("skips graph_period if disabled", func(t *testing.T) {
query := &cloudMonitoringTimeSeriesQuery{GraphPeriod: "disabled"}
assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), "")
})
}

View File

@@ -40,6 +40,7 @@ type (
IntervalMS int64
AliasBy string
timeRange backend.TimeRange
GraphPeriod string
}
metricQuery struct {
@@ -56,6 +57,7 @@ type (
Query string
Preprocessor string
PreprocessorType preprocessorType
GraphPeriod string
}
sloQuery struct {

View File

@@ -2,11 +2,11 @@ import React, { FC } from 'react';
import { SelectableValue } from '@grafana/data';
import { SELECT_WIDTH } from '../constants';
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../constants';
import CloudMonitoringDatasource from '../datasource';
import { CustomMetaData, MetricQuery, SLOQuery } from '../types';
import { AlignmentFunction, AlignmentPeriod, AlignmentPeriodLabel, QueryEditorField, QueryEditorRow } from '.';
import { AlignmentFunction, PeriodSelect, AlignmentPeriodLabel, QueryEditorField, QueryEditorRow } from '.';
export interface Props {
refId: string;
@@ -39,12 +39,13 @@ export const Alignment: FC<Props> = ({
onChange={onChange}
/>
<QueryEditorField label="Alignment period" htmlFor={`${refId}-alignment-period`}>
<AlignmentPeriod
<PeriodSelect
inputId={`${refId}-alignment-period`}
selectWidth={SELECT_WIDTH}
templateVariableOptions={templateVariableOptions}
query={query}
onChange={onChange}
current={query.alignmentPeriod}
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
aligmentPeriods={ALIGNMENT_PERIODS}
/>
</QueryEditorField>
</QueryEditorRow>

View File

@@ -0,0 +1,39 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { select } from 'react-select-event';
import { GraphPeriod, Props } from './GraphPeriod';
const props: Props = {
onChange: jest.fn(),
refId: 'A',
variableOptionGroup: { options: [] },
};
describe('Graph Period', () => {
it('should enable graph_period by default', () => {
render(<GraphPeriod {...props} />);
expect(screen.getByLabelText('Graph period')).not.toBeDisabled();
});
it('should disable graph_period when toggled', async () => {
const onChange = jest.fn();
render(<GraphPeriod {...props} onChange={onChange} />);
const s = screen.getByTestId('A-switch-graph-period');
await userEvent.click(s);
expect(onChange).toHaveBeenCalledWith('disabled');
});
it('should set a different value when selected', async () => {
const onChange = jest.fn();
render(<GraphPeriod {...props} onChange={onChange} />);
const selectEl = screen.getByLabelText('Graph period');
expect(selectEl).toBeInTheDocument();
await select(selectEl, '1m', {
container: document.body,
});
expect(onChange).toHaveBeenCalledWith('1m');
});
});

View File

@@ -0,0 +1,47 @@
import React, { FunctionComponent } from 'react';
import { SelectableValue } from '@grafana/data';
import { Switch } from '@grafana/ui';
import { GRAPH_PERIODS, SELECT_WIDTH } from '../constants';
import { PeriodSelect, QueryEditorRow } from '.';
export interface Props {
refId: string;
onChange: (period: string) => void;
variableOptionGroup: SelectableValue<string>;
graphPeriod?: string;
}
export const GraphPeriod: FunctionComponent<Props> = ({ refId, onChange, graphPeriod, variableOptionGroup }) => {
return (
<>
<QueryEditorRow
label="Graph period"
htmlFor={`${refId}-graph-period`}
tooltip={
<>
Set <code>graph_period</code> which forces a preferred period between points. Automatically set to the
current interval if left blank.
</>
}
>
<Switch
data-testid={`${refId}-switch-graph-period`}
value={graphPeriod !== 'disabled'}
onChange={(e) => onChange(e.currentTarget.checked ? '' : 'disabled')}
/>
<PeriodSelect
inputId={`${refId}-graph-period`}
templateVariableOptions={variableOptionGroup.options}
current={graphPeriod}
onChange={onChange}
selectWidth={SELECT_WIDTH}
disabled={graphPeriod === 'disabled'}
aligmentPeriods={GRAPH_PERIODS}
/>
</QueryEditorRow>
</>
);
};

View File

@@ -16,6 +16,7 @@ import {
ValueTypes,
} from '../types';
import { GraphPeriod } from './GraphPeriod';
import { MQLQueryEditor } from './MQLQueryEditor';
import { AliasBy, Project, VisualMetricQueryEditor } from '.';
@@ -128,11 +129,19 @@ function Editor({
)}
{editorMode === EditorMode.MQL && (
<MQLQueryEditor
onChange={(q: string) => onQueryChange({ ...query, query: q })}
onRunQuery={onRunQuery}
query={query.query}
></MQLQueryEditor>
<>
<MQLQueryEditor
onChange={(q: string) => onQueryChange({ ...query, query: q })}
onRunQuery={onRunQuery}
query={query.query}
></MQLQueryEditor>
<GraphPeriod
onChange={(graphPeriod: string) => onQueryChange({ ...query, graphPeriod })}
graphPeriod={query.graphPeriod}
refId={refId}
variableOptionGroup={variableOptionGroup}
/>
</>
)}
<AliasBy

View File

@@ -3,39 +3,43 @@ import React, { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';
import { ALIGNMENT_PERIODS } from '../constants';
import { MetricQuery, SLOQuery } from '../types';
import { periodOption } from '../constants';
export interface Props<TQuery> {
export interface Props {
inputId: string;
onChange(query: TQuery): void;
query: TQuery;
onChange: (period: string) => void;
templateVariableOptions: Array<SelectableValue<string>>;
aligmentPeriods: periodOption[];
selectWidth?: number;
category?: string;
disabled?: boolean;
current?: string;
}
export function AlignmentPeriod<TQuery extends MetricQuery | SLOQuery>({
export function PeriodSelect({
inputId,
templateVariableOptions,
onChange,
query,
current,
selectWidth,
}: Props<TQuery>) {
disabled,
aligmentPeriods,
}: Props) {
const options = useMemo(
() =>
ALIGNMENT_PERIODS.map((ap) => ({
aligmentPeriods.map((ap) => ({
...ap,
label: ap.text,
})),
[]
[aligmentPeriods]
);
const visibleOptions = useMemo(() => options.filter((ap) => !ap.hidden), [options]);
return (
<Select
width={selectWidth}
onChange={({ value }) => onChange({ ...query, alignmentPeriod: value! })}
value={[...options, ...templateVariableOptions].find((s) => s.value === query.alignmentPeriod)}
onChange={({ value }) => onChange(value!)}
value={[...options, ...templateVariableOptions].find((s) => s.value === current)}
options={[
{
label: 'Template Variables',
@@ -47,8 +51,10 @@ export function AlignmentPeriod<TQuery extends MetricQuery | SLOQuery>({
options: visibleOptions,
},
]}
placeholder="Select Alignment"
placeholder="Select Period"
inputId={inputId}
disabled={disabled}
allowCustomValue
></Select>
);
}

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { SelectableValue } from '@grafana/data';
import { AliasBy, AlignmentPeriod, AlignmentPeriodLabel, Project, QueryEditorRow } from '..';
import { SELECT_WIDTH } from '../../constants';
import { AliasBy, PeriodSelect, AlignmentPeriodLabel, Project, QueryEditorRow } from '..';
import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../../constants';
import CloudMonitoringDatasource from '../../datasource';
import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types';
@@ -71,15 +71,13 @@ export function SLOQueryEditor({
></Selector>
<QueryEditorRow label="Alignment period" htmlFor={`${refId}-alignment-period`}>
<AlignmentPeriod
<PeriodSelect
inputId={`${refId}-alignment-period`}
templateVariableOptions={variableOptionGroup.options}
query={{
...query,
perSeriesAligner: query.selectorName === 'select_slo_health' ? 'ALIGN_MEAN' : 'ALIGN_NEXT_OLDER',
}}
onChange={onChange}
selectWidth={SELECT_WIDTH}
current={query.alignmentPeriod}
onChange={(period) => onChange({ ...query, alignmentPeriod: period })}
aligmentPeriods={ALIGNMENT_PERIODS}
/>
<AlignmentPeriodLabel datasource={datasource} customMetaData={customMetaData} />
</QueryEditorRow>

View File

@@ -5,7 +5,6 @@ export { Alignment } from './Alignment';
export { LabelFilter } from './LabelFilter';
export { AnnotationsHelp } from './AnnotationsHelp';
export { AlignmentFunction } from './AlignmentFunction';
export { AlignmentPeriod } from './AlignmentPeriod';
export { AlignmentPeriodLabel } from './AlignmentPeriodLabel';
export { AliasBy } from './AliasBy';
export { Aggregation } from './Aggregation';
@@ -14,4 +13,5 @@ export { SLOQueryEditor } from './SLO/SLOQueryEditor';
export { MQLQueryEditor } from './MQLQueryEditor';
export { VariableQueryField, QueryEditorRow, QueryEditorField } from './Fields';
export { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
export { PeriodSelect } from './PeriodSelect';
export { Preprocessor } from './Preprocessor';

View File

@@ -226,7 +226,13 @@ export const AGGREGATIONS = [
},
];
export const ALIGNMENT_PERIODS = [
export type periodOption = {
text: string;
value: string;
hidden?: boolean;
};
export const ALIGNMENT_PERIODS: periodOption[] = [
{ text: 'grafana auto', value: 'grafana-auto' },
{ text: 'stackdriver auto', value: 'stackdriver-auto', hidden: true },
{ text: 'cloud monitoring auto', value: 'cloud-monitoring-auto' },
@@ -243,6 +249,21 @@ export const ALIGNMENT_PERIODS = [
{ text: '1w', value: '+604800s' },
];
export const GRAPH_PERIODS: periodOption[] = [
{ text: 'auto', value: 'auto' },
{ text: '1m', value: '1m' },
{ text: '2m', value: '2m' },
{ text: '5m', value: '5m' },
{ text: '10m', value: '10m' },
{ text: '30m', value: '30m' },
{ text: '1h', value: '1h' },
{ text: '3h', value: '3h' },
{ text: '6h', value: '6h' },
{ text: '1d', value: '1d' },
{ text: '3d', value: '3d' },
{ text: '1w', value: '1w' },
];
export const SYSTEM_LABELS = [
'metadata.system_labels.cloud_account',
'metadata.system_labels.name',

View File

@@ -126,6 +126,8 @@ export interface MetricQuery extends BaseQuery {
view?: string;
query: string;
preprocessor?: PreprocessorType;
// To disable the graphPeriod, it should explictly be set to 'disabled'
graphPeriod?: 'disabled' | string;
}
export interface SLOQuery extends BaseQuery {