mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudMonitoring: Allow to set a custom value or disable graph_period (#48646)
This commit is contained in:
committed by
GitHub
parent
6923b4c6c6
commit
b1bde7667f
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{{}}}), "")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user