Cloud Monitoring: Update GroupBy fields to use experimental UI components (#50541)
* Cloud Monitoring: Update GroupBy fields to use experimental UI components * let group by field grow horizontally * remove fixed width constants from inputs * add test * Cloud Monitoring: Update GraphPeriod to use experimental UI components (#50545) * Cloud Monitoring: Update GraphPeriod to use experimental UI components * Cloud Monitoring: Update Preprocessing to use experimental UI components (#50548) * Cloud Monitoring: Update Preprocessing to use experimental UI components * add tests * make overrides optional * move preprocessor back into its own row
This commit is contained in:
+1
-1
@@ -252,7 +252,7 @@
|
|||||||
"@grafana/aws-sdk": "0.0.36",
|
"@grafana/aws-sdk": "0.0.36",
|
||||||
"@grafana/data": "workspace:*",
|
"@grafana/data": "workspace:*",
|
||||||
"@grafana/e2e-selectors": "workspace:*",
|
"@grafana/e2e-selectors": "workspace:*",
|
||||||
"@grafana/experimental": "^0.0.2-canary.30",
|
"@grafana/experimental": "^0.0.2-canary.32",
|
||||||
"@grafana/google-sdk": "0.0.3",
|
"@grafana/google-sdk": "0.0.3",
|
||||||
"@grafana/lezer-logql": "^0.0.12",
|
"@grafana/lezer-logql": "^0.0.12",
|
||||||
"@grafana/runtime": "workspace:*",
|
"@grafana/runtime": "workspace:*",
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { MetricDescriptor, MetricKind, ValueTypes } from '../types';
|
||||||
|
|
||||||
|
export const createMockMetricDescriptor = (overrides?: Partial<MetricDescriptor>): MetricDescriptor => {
|
||||||
|
return {
|
||||||
|
metricKind: MetricKind.CUMULATIVE,
|
||||||
|
valueType: ValueTypes.DOUBLE,
|
||||||
|
type: 'type',
|
||||||
|
unit: 'unit',
|
||||||
|
service: 'service',
|
||||||
|
serviceShortName: 'srv',
|
||||||
|
displayName: 'displayName',
|
||||||
|
description: 'description',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
};
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { openMenu } from 'react-select-event';
|
||||||
|
import { TemplateSrvStub } from 'test/specs/helpers';
|
||||||
|
|
||||||
|
import { ValueTypes, MetricKind } from '../../types';
|
||||||
|
|
||||||
|
import { Aggregation, Props } from './Aggregation';
|
||||||
|
|
||||||
|
const props: Props = {
|
||||||
|
onChange: () => {},
|
||||||
|
// @ts-ignore
|
||||||
|
templateSrv: new TemplateSrvStub(),
|
||||||
|
metricDescriptor: {
|
||||||
|
valueType: '',
|
||||||
|
metricKind: '',
|
||||||
|
} as any,
|
||||||
|
crossSeriesReducer: '',
|
||||||
|
groupBys: [],
|
||||||
|
templateVariableOptions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Aggregation', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
render(<Aggregation {...props} />);
|
||||||
|
expect(screen.getByTestId('cloud-monitoring-aggregation')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('options', () => {
|
||||||
|
describe('when DOUBLE and GAUGE is passed as props', () => {
|
||||||
|
const nextProps = {
|
||||||
|
...props,
|
||||||
|
metricDescriptor: {
|
||||||
|
valueType: ValueTypes.DOUBLE,
|
||||||
|
metricKind: MetricKind.GAUGE,
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should not have the reduce values', () => {
|
||||||
|
render(<Aggregation {...nextProps} />);
|
||||||
|
const label = screen.getByLabelText('Group by function');
|
||||||
|
openMenu(label);
|
||||||
|
expect(screen.queryByText('count true')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('count false')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when MONEY and CUMULATIVE is passed as props', () => {
|
||||||
|
const nextProps = {
|
||||||
|
...props,
|
||||||
|
metricDescriptor: {
|
||||||
|
valueType: ValueTypes.MONEY,
|
||||||
|
metricKind: MetricKind.CUMULATIVE,
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should have the reduce values', () => {
|
||||||
|
render(<Aggregation {...nextProps} />);
|
||||||
|
const label = screen.getByLabelText('Group by function');
|
||||||
|
openMenu(label);
|
||||||
|
expect(screen.getByText('none')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import React, { FC, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField } from '@grafana/experimental';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { getAggregationOptionsByMetric } from '../../functions';
|
||||||
|
import { MetricDescriptor, MetricKind, ValueTypes } from '../../types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
refId: string;
|
||||||
|
onChange: (metricDescriptor: string) => void;
|
||||||
|
metricDescriptor?: MetricDescriptor;
|
||||||
|
crossSeriesReducer: string;
|
||||||
|
groupBys: string[];
|
||||||
|
templateVariableOptions: Array<SelectableValue<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Aggregation: FC<Props> = (props) => {
|
||||||
|
const aggOptions = useAggregationOptionsByMetric(props);
|
||||||
|
const selected = useSelectedFromOptions(aggOptions, props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorField label="Group by function" data-testid="cloud-monitoring-aggregation">
|
||||||
|
<Select
|
||||||
|
width="auto"
|
||||||
|
onChange={({ value }) => props.onChange(value!)}
|
||||||
|
value={selected}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: 'Template Variables',
|
||||||
|
options: props.templateVariableOptions,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Aggregations',
|
||||||
|
expanded: true,
|
||||||
|
options: aggOptions,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
placeholder="Select Reducer"
|
||||||
|
inputId={`${props.refId}-group-by-function`}
|
||||||
|
/>
|
||||||
|
</EditorField>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useAggregationOptionsByMetric = ({ metricDescriptor }: Props): Array<SelectableValue<string>> => {
|
||||||
|
const valueType = metricDescriptor?.valueType;
|
||||||
|
const metricKind = metricDescriptor?.metricKind;
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!valueType || !metricKind) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAggregationOptionsByMetric(valueType as ValueTypes, metricKind as MetricKind).map((a) => ({
|
||||||
|
...a,
|
||||||
|
label: a.text,
|
||||||
|
}));
|
||||||
|
}, [valueType, metricKind]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSelectedFromOptions = (aggOptions: Array<SelectableValue<string>>, props: Props) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const allOptions = [...aggOptions, ...props.templateVariableOptions];
|
||||||
|
return allOptions.find((s) => s.value === props.crossSeriesReducer);
|
||||||
|
}, [aggOptions, props.crossSeriesReducer, props.templateVariableOptions]);
|
||||||
|
};
|
||||||
+39
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField, EditorRow } from '@grafana/experimental';
|
||||||
|
import { HorizontalGroup, Switch } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { GRAPH_PERIODS, SELECT_WIDTH } from '../../constants';
|
||||||
|
import { PeriodSelect } from '../index';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
refId: string;
|
||||||
|
onChange: (period: string) => void;
|
||||||
|
variableOptionGroup: SelectableValue<string>;
|
||||||
|
graphPeriod?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GraphPeriod: FunctionComponent<Props> = ({ refId, onChange, graphPeriod, variableOptionGroup }) => {
|
||||||
|
return (
|
||||||
|
<EditorRow>
|
||||||
|
<EditorField
|
||||||
|
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.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</EditorField>
|
||||||
|
</EditorRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { openMenu, select } from 'react-select-event';
|
||||||
|
|
||||||
|
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||||
|
|
||||||
|
import { GroupBy, Props } from './GroupBy';
|
||||||
|
|
||||||
|
const props: Props = {
|
||||||
|
onChange: jest.fn(),
|
||||||
|
refId: 'refId',
|
||||||
|
metricDescriptor: {
|
||||||
|
valueType: '',
|
||||||
|
metricKind: '',
|
||||||
|
} as any,
|
||||||
|
variableOptionGroup: { options: [] },
|
||||||
|
labels: [],
|
||||||
|
query: createMockMetricQuery(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('GroupBy', () => {
|
||||||
|
it('renders group by fields', () => {
|
||||||
|
render(<GroupBy {...props} />);
|
||||||
|
expect(screen.getByLabelText('Group by')).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('Group by function')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can select a group by', async () => {
|
||||||
|
const onChange = jest.fn();
|
||||||
|
render(<GroupBy {...props} onChange={onChange} />);
|
||||||
|
|
||||||
|
const groupBy = screen.getByLabelText('Group by');
|
||||||
|
const option = 'metadata.system_labels.cloud_account';
|
||||||
|
|
||||||
|
expect(screen.queryByText(option)).not.toBeInTheDocument();
|
||||||
|
await openMenu(groupBy);
|
||||||
|
expect(screen.getByText(option)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await select(groupBy, option, { container: document.body });
|
||||||
|
expect(onChange).toBeCalledWith(expect.objectContaining({ groupBys: expect.arrayContaining([option]) }));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
|
||||||
|
import { MultiSelect } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { SYSTEM_LABELS } from '../../constants';
|
||||||
|
import { labelsToGroupedOptions } from '../../functions';
|
||||||
|
import { MetricDescriptor, MetricQuery } from '../../types';
|
||||||
|
|
||||||
|
import { Aggregation } from './Aggregation';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
refId: string;
|
||||||
|
variableOptionGroup: SelectableValue<string>;
|
||||||
|
labels: string[];
|
||||||
|
metricDescriptor?: MetricDescriptor;
|
||||||
|
onChange: (query: MetricQuery) => void;
|
||||||
|
query: MetricQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupBy: FunctionComponent<Props> = ({
|
||||||
|
refId,
|
||||||
|
labels: groupBys = [],
|
||||||
|
query,
|
||||||
|
onChange,
|
||||||
|
variableOptionGroup,
|
||||||
|
metricDescriptor,
|
||||||
|
}) => {
|
||||||
|
const options = useMemo(
|
||||||
|
() => [variableOptionGroup, ...labelsToGroupedOptions([...groupBys, ...SYSTEM_LABELS])],
|
||||||
|
[groupBys, variableOptionGroup]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorRow>
|
||||||
|
<EditorFieldGroup>
|
||||||
|
<EditorField
|
||||||
|
label="Group by"
|
||||||
|
tooltip="You can reduce the amount of data returned for a metric by combining different time series. To combine multiple time series, you can specify a grouping and a function. Grouping is done on the basis of labels. The grouping function is used to combine the time series in the group into a single time series."
|
||||||
|
>
|
||||||
|
<MultiSelect
|
||||||
|
inputId={`${refId}-group-by`}
|
||||||
|
width="auto"
|
||||||
|
placeholder="Choose label"
|
||||||
|
options={options}
|
||||||
|
value={query.groupBys ?? []}
|
||||||
|
onChange={(options) => {
|
||||||
|
onChange({ ...query, groupBys: options.map((o) => o.value!) });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EditorField>
|
||||||
|
<Aggregation
|
||||||
|
metricDescriptor={metricDescriptor}
|
||||||
|
templateVariableOptions={variableOptionGroup.options}
|
||||||
|
crossSeriesReducer={query.crossSeriesReducer}
|
||||||
|
groupBys={query.groupBys ?? []}
|
||||||
|
onChange={(crossSeriesReducer) => onChange({ ...query, crossSeriesReducer })}
|
||||||
|
refId={refId}
|
||||||
|
/>
|
||||||
|
</EditorFieldGroup>
|
||||||
|
</EditorRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
-1
@@ -18,9 +18,9 @@ import {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { Project } from '../index';
|
import { Project } from '../index';
|
||||||
|
|
||||||
import { GraphPeriod } from './../GraphPeriod';
|
|
||||||
import { MQLQueryEditor } from './../MQLQueryEditor';
|
import { MQLQueryEditor } from './../MQLQueryEditor';
|
||||||
import { AliasBy } from './AliasBy';
|
import { AliasBy } from './AliasBy';
|
||||||
|
import { GraphPeriod } from './GraphPeriod';
|
||||||
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
import { VisualMetricQueryEditor } from './VisualMetricQueryEditor';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { TemplateSrvMock } from 'app/features/templating/template_srv.mock';
|
||||||
|
|
||||||
|
import { createMockMetricDescriptor } from '../../__mocks__/cloudMonitoringMetricDescriptor';
|
||||||
|
import { createMockMetricQuery } from '../../__mocks__/cloudMonitoringQuery';
|
||||||
|
import { MetricKind, ValueTypes } from '../../types';
|
||||||
|
|
||||||
|
import { Preprocessor } from './Preprocessor';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
getTemplateSrv: () => new TemplateSrvMock({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Preprocessor', () => {
|
||||||
|
it('only provides "None" as an option if no metric descriptor is provided', () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} />);
|
||||||
|
expect(screen.getByText('Pre-processing')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('None')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Rate')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delta')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only provides "None" as an option if metric kind is "Gauge"', () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const metricDescriptor = createMockMetricDescriptor({ metricKind: MetricKind.GAUGE });
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} metricDescriptor={metricDescriptor} />);
|
||||||
|
expect(screen.getByText('Pre-processing')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('None')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Rate')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delta')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only provides "None" as an option if value type is "Distribution"', () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const metricDescriptor = createMockMetricDescriptor({ valueType: ValueTypes.DISTRIBUTION });
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} metricDescriptor={metricDescriptor} />);
|
||||||
|
expect(screen.getByText('Pre-processing')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('None')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Rate')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delta')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides "None" and "Rate" as options if metric kind is not "Delta" or "Cumulative" and value type is not "Distribution"', () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const metricDescriptor = createMockMetricDescriptor({ metricKind: MetricKind.DELTA });
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} metricDescriptor={metricDescriptor} />);
|
||||||
|
expect(screen.getByText('Pre-processing')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('None')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Rate')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delta')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides all options if metric kind is "Cumulative" and value type is not "Distribution"', () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const metricDescriptor = createMockMetricDescriptor({ metricKind: MetricKind.CUMULATIVE });
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} metricDescriptor={metricDescriptor} />);
|
||||||
|
expect(screen.getByText('Pre-processing')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('None')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Rate')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delta')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides all options if metric kind is "Cumulative" and value type is not "Distribution"', async () => {
|
||||||
|
const query = createMockMetricQuery();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
const metricDescriptor = createMockMetricDescriptor({ metricKind: MetricKind.CUMULATIVE });
|
||||||
|
|
||||||
|
render(<Preprocessor onChange={onChange} query={query} metricDescriptor={metricDescriptor} />);
|
||||||
|
const none = screen.getByLabelText('None');
|
||||||
|
const rate = screen.getByLabelText('Rate');
|
||||||
|
const delta = screen.getByLabelText('Delta');
|
||||||
|
expect(none).toBeChecked();
|
||||||
|
expect(rate).not.toBeChecked();
|
||||||
|
expect(delta).not.toBeChecked();
|
||||||
|
|
||||||
|
await userEvent.click(rate);
|
||||||
|
|
||||||
|
expect(onChange).toBeCalledWith(expect.objectContaining({ preprocessor: 'rate' }));
|
||||||
|
});
|
||||||
|
});
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorField, EditorRow } from '@grafana/experimental';
|
||||||
|
import { RadioButtonGroup } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { getAlignmentPickerData } from '../../functions';
|
||||||
|
import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../../types';
|
||||||
|
|
||||||
|
const NONE_OPTION = { label: 'None', value: PreprocessorType.None };
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
metricDescriptor?: MetricDescriptor;
|
||||||
|
onChange: (query: MetricQuery) => void;
|
||||||
|
query: MetricQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Preprocessor: FunctionComponent<Props> = ({ query, metricDescriptor, onChange }) => {
|
||||||
|
const options = useOptions(metricDescriptor);
|
||||||
|
return (
|
||||||
|
<EditorRow>
|
||||||
|
<EditorField
|
||||||
|
label="Pre-processing"
|
||||||
|
tooltip="Preprocessing options are displayed when the selected metric has a metric kind of delta or cumulative. The specific options available are determined by the metic's value type. If you select 'Rate', data points are aligned and converted to a rate per time series. If you select 'Delta', data points are aligned by their delta (difference) per time series"
|
||||||
|
>
|
||||||
|
<RadioButtonGroup
|
||||||
|
onChange={(value: PreprocessorType) => {
|
||||||
|
const { valueType, metricKind, perSeriesAligner: psa } = query;
|
||||||
|
const { perSeriesAligner } = getAlignmentPickerData(valueType, metricKind, psa, value);
|
||||||
|
onChange({ ...query, preprocessor: value, perSeriesAligner });
|
||||||
|
}}
|
||||||
|
value={query.preprocessor ?? PreprocessorType.None}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
</EditorField>
|
||||||
|
</EditorRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useOptions = (metricDescriptor?: MetricDescriptor): Array<SelectableValue<PreprocessorType>> => {
|
||||||
|
const metricKind = metricDescriptor?.metricKind;
|
||||||
|
const valueType = metricDescriptor?.valueType;
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!metricKind || metricKind === MetricKind.GAUGE || valueType === ValueTypes.DISTRIBUTION) {
|
||||||
|
return [NONE_OPTION];
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
NONE_OPTION,
|
||||||
|
{
|
||||||
|
label: 'Rate',
|
||||||
|
value: PreprocessorType.Rate,
|
||||||
|
description: 'Data points are aligned and converted to a rate per time series',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return metricKind === MetricKind.CUMULATIVE
|
||||||
|
? [
|
||||||
|
...options,
|
||||||
|
{
|
||||||
|
label: 'Delta',
|
||||||
|
value: PreprocessorType.Delta,
|
||||||
|
description: 'Data points are aligned by their delta (difference) per time series',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: options;
|
||||||
|
}, [metricKind, valueType]);
|
||||||
|
};
|
||||||
+3
-1
@@ -4,9 +4,11 @@ import { SelectableValue } from '@grafana/data';
|
|||||||
|
|
||||||
import CloudMonitoringDatasource from '../../datasource';
|
import CloudMonitoringDatasource from '../../datasource';
|
||||||
import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../../types';
|
import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../../types';
|
||||||
import { GroupBy, LabelFilter, Metrics, Preprocessor } from '../index';
|
import { LabelFilter, Metrics } from '../index';
|
||||||
|
|
||||||
import { Alignment } from './Alignment';
|
import { Alignment } from './Alignment';
|
||||||
|
import { GroupBy } from './GroupBy';
|
||||||
|
import { Preprocessor } from './Preprocessor';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
refId: string;
|
refId: string;
|
||||||
|
|||||||
@@ -4463,9 +4463,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@grafana/experimental@npm:^0.0.2-canary.30":
|
"@grafana/experimental@npm:^0.0.2-canary.32":
|
||||||
version: 0.0.2-canary.30
|
version: 0.0.2-canary.32
|
||||||
resolution: "@grafana/experimental@npm:0.0.2-canary.30"
|
resolution: "@grafana/experimental@npm:0.0.2-canary.32"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/uuid": ^8.3.3
|
"@types/uuid": ^8.3.3
|
||||||
uuid: ^8.3.2
|
uuid: ^8.3.2
|
||||||
@@ -4473,7 +4473,7 @@ __metadata:
|
|||||||
"@emotion/css": 11.1.3
|
"@emotion/css": 11.1.3
|
||||||
react: 17.0.1
|
react: 17.0.1
|
||||||
react-select: 5.2.1
|
react-select: 5.2.1
|
||||||
checksum: b5b453b9372cde8f89021c50ae1191a2506ebbb069ad6331d22a5c7267ecd8c0db7f9f5fdd9cfab49fafce4e0b6809609e67449b78fe3ccc63cedfeceb64b911
|
checksum: 71924e6d03335fbedf1553ce08c2bbe95819ad746af48728aa81923f3f8ca0f0e8cf3db979095da5048f2b256e96fbb88aa8efb480e84913d81dd66faefeadec
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -20646,7 +20646,7 @@ __metadata:
|
|||||||
"@grafana/e2e": "workspace:*"
|
"@grafana/e2e": "workspace:*"
|
||||||
"@grafana/e2e-selectors": "workspace:*"
|
"@grafana/e2e-selectors": "workspace:*"
|
||||||
"@grafana/eslint-config": 3.0.0
|
"@grafana/eslint-config": 3.0.0
|
||||||
"@grafana/experimental": ^0.0.2-canary.30
|
"@grafana/experimental": ^0.0.2-canary.32
|
||||||
"@grafana/google-sdk": 0.0.3
|
"@grafana/google-sdk": 0.0.3
|
||||||
"@grafana/lezer-logql": ^0.0.12
|
"@grafana/lezer-logql": ^0.0.12
|
||||||
"@grafana/runtime": "workspace:*"
|
"@grafana/runtime": "workspace:*"
|
||||||
|
|||||||
Reference in New Issue
Block a user