diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts b/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts index f05fe79d8ae..f9ffeba295b 100644 --- a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts +++ b/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts @@ -1,12 +1,19 @@ +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TemplateSrvMock } from 'app/features/templating/template_srv.mock'; + import Datasource from '../datasource'; -export const createMockDatasource = () => { +export const createMockDatasource = (overrides?: Partial) => { + const templateSrv = new TemplateSrvMock({ ALIGN_DELTA: 'delta' }) as unknown as TemplateSrv; + const datasource: Partial = { intervalMs: 0, getVariables: jest.fn().mockReturnValue([]), getMetricTypes: jest.fn().mockResolvedValue([]), getProjects: jest.fn().mockResolvedValue([]), getDefaultProject: jest.fn().mockReturnValue('cloud-monitoring-default-project'), + templateSrv, + ...overrides, }; return jest.mocked(datasource as Datasource, true); diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts b/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts index c9a41c09274..d2943f326d8 100644 --- a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts +++ b/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts @@ -1,12 +1,15 @@ import { CloudMonitoringQuery, EditorMode, MetricQuery, QueryType } from '../types'; -export const createMockMetricQuery: () => MetricQuery = () => { +export const createMockMetricQuery: (overrides?: Partial) => MetricQuery = ( + overrides?: Partial +) => { return { editorMode: EditorMode.Visual, metricType: '', crossSeriesReducer: 'REDUCE_NONE', query: '', projectName: 'cloud-monitoring-default-project', + ...overrides, }; }; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.test.tsx new file mode 100644 index 00000000000..a81039f9b03 --- /dev/null +++ b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.test.tsx @@ -0,0 +1,102 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { openMenu } from 'react-select-event'; + +import { TemplateSrvMock } from 'app/features/templating/template_srv.mock'; + +import { createMockDatasource } from '../../__mocks__/cloudMonitoringDatasource'; +import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery'; +import { MetricKind, ValueTypes } from '../../types'; + +import { Alignment } from './Alignment'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getTemplateSrv: () => new TemplateSrvMock({}), +})); + +describe('Alignment', () => { + it('renders alignment fields', () => { + const datasource = createMockDatasource(); + const query = createMockMetricQuery(); + const onChange = jest.fn(); + + render( + + ); + + expect(screen.getByLabelText('Alignment function')).toBeInTheDocument(); + expect(screen.getByLabelText('Alignment period')).toBeInTheDocument(); + }); + + it('can set the alignment function', async () => { + const datasource = createMockDatasource(); + const query = createMockMetricQuery({ metricKind: MetricKind.GAUGE, valueType: ValueTypes.INT64 }); + const onChange = jest.fn(); + + render( + + ); + + const alignmentFunction = screen.getByLabelText('Alignment function'); + openMenu(alignmentFunction); + await userEvent.click(screen.getByText('percent change')); + expect(onChange).toBeCalledWith(expect.objectContaining({ perSeriesAligner: 'ALIGN_PERCENT_CHANGE' })); + }); + + it('can set the alignment period', async () => { + const datasource = createMockDatasource(); + const query = createMockMetricQuery(); + const onChange = jest.fn(); + + render( + + ); + + const alignmentPeriod = screen.getByLabelText('Alignment period'); + openMenu(alignmentPeriod); + await userEvent.click(screen.getByText('1m')); + expect(onChange).toBeCalledWith(expect.objectContaining({ alignmentPeriod: '+60s' })); + }); + + it('renders period label if alignment period and per series aligner is set', () => { + const datasource = createMockDatasource(); + const query = createMockMetricQuery(); + const onChange = jest.fn(); + + render( + + ); + + expect(screen.getByText('10s interval (delta)')); + }); +}); diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.tsx new file mode 100644 index 00000000000..10e091fd291 --- /dev/null +++ b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/Alignment.tsx @@ -0,0 +1,61 @@ +import React, { FC } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { EditorRow, EditorField, EditorFieldGroup, Stack } from '@grafana/experimental'; + +import { ALIGNMENT_PERIODS, SELECT_WIDTH } from '../../constants'; +import CloudMonitoringDatasource from '../../datasource'; +import { CustomMetaData, MetricQuery, SLOQuery } from '../../types'; + +import { AlignmentFunction } from './AlignmentFunction'; +import { AlignmentPeriodLabel } from './AlignmentPeriodLabel'; +import { PeriodSelect } from './PeriodSelect'; + +export interface Props { + refId: string; + onChange: (query: MetricQuery | SLOQuery) => void; + query: MetricQuery; + templateVariableOptions: Array>; + customMetaData: CustomMetaData; + datasource: CloudMonitoringDatasource; +} + +export const Alignment: FC = ({ + refId, + templateVariableOptions, + onChange, + query, + customMetaData, + datasource, +}) => { + return ( + + + + + + + onChange({ ...query, alignmentPeriod: period })} + aligmentPeriods={ALIGNMENT_PERIODS} + /> + + + + + + + ); +}; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Experimental/AlignmentFunction.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/AlignmentFunction.tsx new file mode 100644 index 00000000000..49025f84028 --- /dev/null +++ b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/AlignmentFunction.tsx @@ -0,0 +1,44 @@ +import React, { FC, useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + +import { SELECT_WIDTH } from '../../constants'; +import { getAlignmentPickerData } from '../../functions'; +import { MetricQuery } from '../../types'; + +export interface Props { + inputId: string; + onChange: (query: MetricQuery) => void; + query: MetricQuery; + templateVariableOptions: Array>; +} + +export const AlignmentFunction: FC = ({ inputId, query, templateVariableOptions, onChange }) => { + const { valueType, metricKind, perSeriesAligner: psa, preprocessor } = query; + const { perSeriesAligner, alignOptions } = useMemo( + () => getAlignmentPickerData(valueType, metricKind, psa, preprocessor), + [valueType, metricKind, psa, preprocessor] + ); + + return ( + onChange(value!)} + value={[...options, ...templateVariableOptions].find((s) => s.value === current)} + options={[ + { + label: 'Template Variables', + options: templateVariableOptions, + }, + { + label: 'Aggregations', + expanded: true, + options: visibleOptions, + }, + ]} + placeholder="Select Period" + inputId={inputId} + disabled={disabled} + allowCustomValue + /> + ); +} diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Experimental/VisualMetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/VisualMetricQueryEditor.tsx new file mode 100644 index 00000000000..cca8dc1ee8f --- /dev/null +++ b/public/app/plugins/datasource/cloud-monitoring/components/Experimental/VisualMetricQueryEditor.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import { SelectableValue } from '@grafana/data'; + +import CloudMonitoringDatasource from '../../datasource'; +import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../../types'; +import { GroupBy, LabelFilter, Metrics, Preprocessor } from '../index'; + +import { Alignment } from './Alignment'; + +export interface Props { + refId: string; + customMetaData: CustomMetaData; + variableOptionGroup: SelectableValue; + onMetricTypeChange: (query: MetricDescriptor) => void; + onChange: (query: MetricQuery | SLOQuery) => void; + query: MetricQuery; + datasource: CloudMonitoringDatasource; + labels: any; +} + +function Editor({ + refId, + query, + labels, + datasource, + onChange, + onMetricTypeChange, + customMetaData, + variableOptionGroup, +}: React.PropsWithChildren) { + return ( + + {(metric) => ( + <> + onChange({ ...query, filters })} + variableOptionGroup={variableOptionGroup} + /> + + + + + )} + + ); +} + +export const VisualMetricQueryEditor = React.memo(Editor);