From e5f92c010d8b292cf810f25a8bfdcd89421bf2da Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Mon, 30 Oct 2023 15:30:25 +0100 Subject: [PATCH] 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 --- .../datasource/elasticsearch/QueryBuilder.ts | 4 +- .../DateHistogramSettingsEditor.test.tsx | 87 +++++++++++++++++++ .../DateHistogramSettingsEditor.tsx | 33 +++++-- .../SettingsEditor/index.tsx | 2 +- 4 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx diff --git a/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts b/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts index 59a7158c0cf..f398b87fa39 100644 --- a/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts +++ b/public/app/plugins/datasource/elasticsearch/QueryBuilder.ts @@ -26,6 +26,9 @@ import { } from './types'; 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 { timeField: string; @@ -100,7 +103,6 @@ export class ElasticQueryBuilder { getDateHistogramAgg(aggDef: DateHistogram) { const esAgg: any = {}; const settings = aggDef.settings || {}; - const calendarIntervals: string[] = ['1w', '1M', '1q', '1y']; esAgg.field = aggDef.field || this.timeField; esAgg.min_doc_count = settings.min_doc_count || 0; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx new file mode 100644 index 00000000000..9389f779bf7 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.test.tsx @@ -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(); + 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(); + 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(); + 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(); + + 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(); + + expect(await screen.findByText('Fixed interval')).toBeInTheDocument(); + expect(await screen.findByText('1m')).toBeInTheDocument(); + + await selectOptionInTest(screen.getByLabelText('Fixed interval'), '1q'); + + expect(dispatch).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx index 5b5f1c5f03d..7fd36121783 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx @@ -1,9 +1,10 @@ import { uniqueId } from 'lodash'; -import React, { useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import { GroupBase, OptionsOrGroups } from 'react-select'; import { InternalTimeZones, SelectableValue } from '@grafana/data'; import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui'; +import { calendarIntervals } from 'app/plugins/datasource/elasticsearch/QueryBuilder'; import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { DateHistogram } from '../../../../types'; @@ -22,6 +23,10 @@ const defaultIntervalOptions: Array> = [ { label: '20m', value: '20m' }, { label: '1h', value: '1h' }, { label: '1d', value: '1d' }, + { label: '1w', value: '1w' }, + { label: '1M', value: '1M' }, + { label: '1q', value: '1q' }, + { label: '1y', value: '1y' }, ]; const hasValue = @@ -48,16 +53,35 @@ interface Props { bucketAgg: DateHistogram; } +const getIntervalType = (interval: string | undefined): 'calendar' | 'fixed' => { + return interval && calendarIntervals.includes(interval) ? 'calendar' : 'fixed'; +}; + export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => { const dispatch = useDispatch(); const { current: baseId } = useRef(uniqueId('es-date_histogram-')); - const handleIntervalChange = ({ value }: SelectableValue) => - dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue: value })); + const handleIntervalChange = useCallback( + ({ value }: SelectableValue) => + dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue: value })), + [bucketAgg, dispatch] + ); + + const intervalType = getIntervalType( + bucketAgg.settings?.interval || bucketAggregationConfig.date_histogram.defaultSettings?.interval + ); return ( <> - + > = { - labelWidth: 16, + labelWidth: 18, }; interface Props {