Elasticsearch: Add interval type selector to interval field (#76805)
* Elasticsearch: add interval type selector * Interval type: add tooltip * DateHistogramSettingsEditor: create unit test * Elastic histogram settings: refactor showing calendar type * Update test * Prettier * DateHistogramSettingsEditor: change tooltip according to interval type * Calendar intervals: add comment * Prettier
This commit is contained in:
@@ -26,6 +26,9 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { convertOrderByToMetricId, getScriptValue } from './utils';
|
import { convertOrderByToMetricId, getScriptValue } from './utils';
|
||||||
|
|
||||||
|
// Omitting 1m, 1h, 1d for now, as these cover the main use cases for calendar_interval
|
||||||
|
export const calendarIntervals: string[] = ['1w', '1M', '1q', '1y'];
|
||||||
|
|
||||||
export class ElasticQueryBuilder {
|
export class ElasticQueryBuilder {
|
||||||
timeField: string;
|
timeField: string;
|
||||||
|
|
||||||
@@ -100,7 +103,6 @@ export class ElasticQueryBuilder {
|
|||||||
getDateHistogramAgg(aggDef: DateHistogram) {
|
getDateHistogramAgg(aggDef: DateHistogram) {
|
||||||
const esAgg: any = {};
|
const esAgg: any = {};
|
||||||
const settings = aggDef.settings || {};
|
const settings = aggDef.settings || {};
|
||||||
const calendarIntervals: string[] = ['1w', '1M', '1q', '1y'];
|
|
||||||
|
|
||||||
esAgg.field = aggDef.field || this.timeField;
|
esAgg.field = aggDef.field || this.timeField;
|
||||||
esAgg.min_doc_count = settings.min_doc_count || 0;
|
esAgg.min_doc_count = settings.min_doc_count || 0;
|
||||||
|
|||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||||
|
|
||||||
|
import { DateHistogram } from 'app/plugins/datasource/elasticsearch/types';
|
||||||
|
|
||||||
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
|
|
||||||
|
import { DateHistogramSettingsEditor } from './DateHistogramSettingsEditor';
|
||||||
|
|
||||||
|
jest.mock('../../../../hooks/useStatelessReducer');
|
||||||
|
|
||||||
|
describe('DateHistogramSettingsEditor', () => {
|
||||||
|
test('Renders the date histogram selector', async () => {
|
||||||
|
const bucketAgg: DateHistogram = {
|
||||||
|
field: '@timestamp',
|
||||||
|
id: '2',
|
||||||
|
settings: { interval: 'auto' },
|
||||||
|
type: 'date_histogram',
|
||||||
|
};
|
||||||
|
render(<DateHistogramSettingsEditor bucketAgg={bucketAgg} />);
|
||||||
|
expect(await screen.findByText('Fixed interval')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('auto')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
test('Renders the date histogram selector with a fixed interval', async () => {
|
||||||
|
const bucketAgg: DateHistogram = {
|
||||||
|
field: '@timestamp',
|
||||||
|
id: '2',
|
||||||
|
settings: { interval: '10s' },
|
||||||
|
type: 'date_histogram',
|
||||||
|
};
|
||||||
|
render(<DateHistogramSettingsEditor bucketAgg={bucketAgg} />);
|
||||||
|
expect(await screen.findByText('Fixed interval')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('10s')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
test('Renders the date histogram selector with a calendar interval', async () => {
|
||||||
|
const bucketAgg: DateHistogram = {
|
||||||
|
field: '@timestamp',
|
||||||
|
id: '2',
|
||||||
|
settings: { interval: '1w' },
|
||||||
|
type: 'date_histogram',
|
||||||
|
};
|
||||||
|
render(<DateHistogramSettingsEditor bucketAgg={bucketAgg} />);
|
||||||
|
expect(await screen.findByText('Calendar interval')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('1w')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Handling change', () => {
|
||||||
|
let dispatch = jest.fn();
|
||||||
|
beforeEach(() => {
|
||||||
|
dispatch.mockClear();
|
||||||
|
jest.mocked(useDispatch).mockReturnValue(dispatch);
|
||||||
|
});
|
||||||
|
test('Handles changing from calendar to fixed interval type', async () => {
|
||||||
|
const bucketAgg: DateHistogram = {
|
||||||
|
field: '@timestamp',
|
||||||
|
id: '2',
|
||||||
|
settings: { interval: '1w' },
|
||||||
|
type: 'date_histogram',
|
||||||
|
};
|
||||||
|
render(<DateHistogramSettingsEditor bucketAgg={bucketAgg} />);
|
||||||
|
|
||||||
|
expect(await screen.findByText('Calendar interval')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('1w')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Calendar interval'), '10s');
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
test('Renders the date histogram selector with a calendar interval', async () => {
|
||||||
|
const bucketAgg: DateHistogram = {
|
||||||
|
field: '@timestamp',
|
||||||
|
id: '2',
|
||||||
|
settings: { interval: '1m' },
|
||||||
|
type: 'date_histogram',
|
||||||
|
};
|
||||||
|
render(<DateHistogramSettingsEditor bucketAgg={bucketAgg} />);
|
||||||
|
|
||||||
|
expect(await screen.findByText('Fixed interval')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('1m')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await selectOptionInTest(screen.getByLabelText('Fixed interval'), '1q');
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+28
-5
@@ -1,9 +1,10 @@
|
|||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import React, { useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
import { GroupBase, OptionsOrGroups } from 'react-select';
|
import { GroupBase, OptionsOrGroups } from 'react-select';
|
||||||
|
|
||||||
import { InternalTimeZones, SelectableValue } from '@grafana/data';
|
import { InternalTimeZones, SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui';
|
import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui';
|
||||||
|
import { calendarIntervals } from 'app/plugins/datasource/elasticsearch/QueryBuilder';
|
||||||
|
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { DateHistogram } from '../../../../types';
|
import { DateHistogram } from '../../../../types';
|
||||||
@@ -22,6 +23,10 @@ const defaultIntervalOptions: Array<SelectableValue<string>> = [
|
|||||||
{ label: '20m', value: '20m' },
|
{ label: '20m', value: '20m' },
|
||||||
{ label: '1h', value: '1h' },
|
{ label: '1h', value: '1h' },
|
||||||
{ label: '1d', value: '1d' },
|
{ label: '1d', value: '1d' },
|
||||||
|
{ label: '1w', value: '1w' },
|
||||||
|
{ label: '1M', value: '1M' },
|
||||||
|
{ label: '1q', value: '1q' },
|
||||||
|
{ label: '1y', value: '1y' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const hasValue =
|
const hasValue =
|
||||||
@@ -48,16 +53,35 @@ interface Props {
|
|||||||
bucketAgg: DateHistogram;
|
bucketAgg: DateHistogram;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getIntervalType = (interval: string | undefined): 'calendar' | 'fixed' => {
|
||||||
|
return interval && calendarIntervals.includes(interval) ? 'calendar' : 'fixed';
|
||||||
|
};
|
||||||
|
|
||||||
export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
|
export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { current: baseId } = useRef(uniqueId('es-date_histogram-'));
|
const { current: baseId } = useRef(uniqueId('es-date_histogram-'));
|
||||||
|
|
||||||
const handleIntervalChange = ({ value }: SelectableValue<string>) =>
|
const handleIntervalChange = useCallback(
|
||||||
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue: value }));
|
({ value }: SelectableValue<string>) =>
|
||||||
|
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue: value })),
|
||||||
|
[bucketAgg, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const intervalType = getIntervalType(
|
||||||
|
bucketAgg.settings?.interval || bucketAggregationConfig.date_histogram.defaultSettings?.interval
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InlineField label="Interval" {...inlineFieldProps}>
|
<InlineField
|
||||||
|
label={intervalType === 'calendar' ? 'Calendar interval' : 'Fixed interval'}
|
||||||
|
tooltip={
|
||||||
|
intervalType === 'calendar'
|
||||||
|
? 'Calendar-aware intervals adapt to varying day lengths, month durations, and leap seconds, considering the calendar context.'
|
||||||
|
: 'Fixed intervals remain constant, always being multiples of SI units, independent of calendar changes.'
|
||||||
|
}
|
||||||
|
{...inlineFieldProps}
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
inputId={uniqueId('es-date_histogram-interval')}
|
inputId={uniqueId('es-date_histogram-interval')}
|
||||||
isValidNewOption={isValidNewOption}
|
isValidNewOption={isValidNewOption}
|
||||||
@@ -69,7 +93,6 @@ export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
|
|
||||||
<InlineField label="Min Doc Count" {...inlineFieldProps}>
|
<InlineField label="Min Doc Count" {...inlineFieldProps}>
|
||||||
<Input
|
<Input
|
||||||
id={`${baseId}-min_doc_count`}
|
id={`${baseId}-min_doc_count`}
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ import { TermsSettingsEditor } from './TermsSettingsEditor';
|
|||||||
import { useDescription } from './useDescription';
|
import { useDescription } from './useDescription';
|
||||||
|
|
||||||
export const inlineFieldProps: Partial<ComponentProps<typeof InlineField>> = {
|
export const inlineFieldProps: Partial<ComponentProps<typeof InlineField>> = {
|
||||||
labelWidth: 16,
|
labelWidth: 18,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
Reference in New Issue
Block a user