From f8faa8fa4308c90b993dde4561f533ed94544986 Mon Sep 17 00:00:00 2001 From: Melissa Date: Wed, 19 Apr 2023 12:10:44 -0600 Subject: [PATCH 1/5] create custom time range picker component for dfa custom urls --- .../custom_time_range_picker.tsx | 160 ++++++++++++++++++ .../custom_urls/custom_url_editor/editor.tsx | 23 ++- .../custom_urls/custom_url_editor/utils.ts | 25 ++- .../components/custom_urls/custom_urls.tsx | 1 + 4 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx new file mode 100644 index 0000000000000..db23af1b6d43a --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useMemo, useState } from 'react'; +import moment, { type Moment } from 'moment'; +import { + EuiDatePicker, + EuiDatePickerRange, + EuiFlexItem, + EuiFlexGroup, + EuiFormRow, + EuiIconTip, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../../../contexts/kibana'; + +interface CustomUrlTimeRangePickerProps { + onCustomTimeRangeChange: (customTimeRange?: { start: Moment; end: Moment }) => void; + customTimeRange?: { start: Moment; end: Moment }; +} + +/* + * React component for the form for adding a custom time range. + */ +export const CustomTimeRangePicker: FC = ({ + onCustomTimeRangeChange, + customTimeRange, +}) => { + const [showCustomTimeRangeSelector, setShowCustomTimeRangeSelectorked1] = + useState(false); + const { + services: { + data: { + query: { + timefilter: { timefilter }, + }, + }, + }, + } = useMlKibana(); + + const onCustomTimeRangeSwitchChange = (e: { + target: { checked: React.SetStateAction }; + }) => { + if (e.target.checked === false) { + // Clear the custom time range so it isn't persisted + onCustomTimeRangeChange(undefined); + } + setShowCustomTimeRangeSelectorked1(e.target.checked); + }; + + // If the custom time range is not set, default to the timefilter settings + const currentTimeRange = useMemo( + () => + customTimeRange ?? { + start: moment(timefilter.getAbsoluteTime().from), + end: moment(timefilter.getAbsoluteTime().to), + }, + [customTimeRange, timefilter] + ); + + const handleStartChange = (date: moment.Moment) => { + onCustomTimeRangeChange({ ...currentTimeRange, start: date }); + }; + const handleEndChange = (date: moment.Moment) => { + onCustomTimeRangeChange({ ...currentTimeRange, end: date }); + }; + + const { start, end } = currentTimeRange; + + return ( + <> + + + + + + + + } + > + + } + checked={showCustomTimeRangeSelector} + onChange={onCustomTimeRangeSwitchChange} + compressed + /> + + + + {showCustomTimeRangeSelector ? ( + <> + + + } + > + end} + startDateControl={ + + } + endDateControl={ + + } + /> + + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index 9eddc02b5e1a4..d1634567ff025 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -6,6 +6,7 @@ */ import React, { ChangeEvent, FC } from 'react'; +import { type Moment } from 'moment'; import { EuiComboBox, @@ -25,11 +26,13 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewListItem } from '@kbn/data-views-plugin/common'; +// import { isDataFrameAnalyticsConfigs } from '../../../../../common/types/data_frame_analytics'; import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils'; import { isValidLabel } from '../../../util/custom_url_utils'; import { TIME_RANGE_TYPE, TimeRangeType, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; +import { CustomTimeRangePicker } from './custom_time_range_picker'; function getLinkToOptions() { return [ @@ -61,7 +64,8 @@ interface CustomUrlEditorProps { dashboards: Array<{ id: string; title: string }>; dataViewListItems: DataViewListItem[]; queryEntityFieldNames: string[]; - showTimeRangeSelector?: boolean; + showTimeRangeSelector: boolean; + showCustomTimeRangeSelector: boolean; } /* @@ -74,7 +78,8 @@ export const CustomUrlEditor: FC = ({ dashboards, dataViewListItems, queryEntityFieldNames, - showTimeRangeSelector = true, + showTimeRangeSelector, + showCustomTimeRangeSelector, }) => { if (customUrl === undefined) { return null; @@ -94,6 +99,13 @@ export const CustomUrlEditor: FC = ({ }); }; + const onCustomTimeRangeChange = (timeRange?: { start: Moment; end: Moment }) => { + setEditCustomUrl({ + ...customUrl, + customTimeRange: timeRange, + }); + }; + const onDashboardChange = (e: ChangeEvent) => { const kibanaSettings = customUrl.kibanaSettings; setEditCustomUrl({ @@ -306,6 +318,13 @@ export const CustomUrlEditor: FC = ({ /> )} + {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && + showCustomTimeRangeSelector ? ( + + ) : null} {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && showTimeRangeSelector && ( diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts index 2f1cd59bcfe50..1467d52f3afbc 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts @@ -6,6 +6,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { Moment } from 'moment'; import type { SerializableRecord } from '@kbn/utility-types'; import rison from '@kbn/rison'; import url from 'url'; @@ -57,6 +58,7 @@ export interface CustomUrlSettings { // Note timeRange is only editable in new URLs for Dashboard and Discover URLs, // as for other URLs we have no way of knowing how the field will be used in the URL. timeRange: TimeRange; + customTimeRange?: { start: Moment; end: Moment }; kibanaSettings?: { dashboardId?: string; queryFieldNames?: string[]; @@ -239,12 +241,19 @@ async function buildDashboardUrlFromSettings(settings: CustomUrlSettings): Promi } const dashboard = getDashboard(); + let customStart; + let customEnd; + + if (settings.customTimeRange && settings.customTimeRange.start && settings.customTimeRange.end) { + customStart = settings.customTimeRange.start.toISOString(); + customEnd = settings.customTimeRange.end.toISOString(); + } const location = await dashboard?.locator?.getLocation({ dashboardId, timeRange: { - from: '$earliest$', - to: '$latest$', + from: customStart ?? '$earliest$', + to: customEnd ?? '$latest$', mode: 'absolute', }, filters, @@ -286,10 +295,18 @@ function buildDiscoverUrlFromSettings(settings: CustomUrlSettings) { // Add time settings to the global state URL parameter with $earliest$ and // $latest$ tokens which get substituted for times around the time of the // anomaly on which the URL will be run against. + let customStart; + let customEnd; + + if (settings.customTimeRange && settings.customTimeRange.start && settings.customTimeRange.end) { + customStart = settings.customTimeRange.start.toISOString(); + customEnd = settings.customTimeRange.end.toISOString(); + } + const _g = rison.encode({ time: { - from: '$earliest$', - to: '$latest$', + from: customStart ?? '$earliest$', + to: customEnd ?? '$latest$', mode: 'absolute', }, }); diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx index 7ebab7b3a1359..5419d2f82cfef 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx @@ -236,6 +236,7 @@ class CustomUrlsUI extends Component { const editor = ( Date: Wed, 19 Apr 2023 19:43:07 -0600 Subject: [PATCH 2/5] fix jest tests --- .../components/custom_urls/custom_url_editor/editor.test.tsx | 5 +++++ .../components/custom_urls/custom_url_editor/editor.tsx | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx index ce7f31df4e86c..031900c9550bb 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.test.tsx @@ -16,6 +16,7 @@ import { CustomUrlEditor } from './editor'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { CustomUrlSettings } from './utils'; import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { Job } from '../../../../../common/types/anomaly_detection_jobs'; function prepareTest( customUrl: CustomUrlSettings, @@ -55,6 +56,7 @@ function prepareTest( ] as DataViewListItem[]; const queryEntityFieldNames = ['airline']; + const job = { job_id: 'id', analysis_config: { influencers: ['airline'] } } as Job; const props = { customUrl, @@ -63,6 +65,9 @@ function prepareTest( dashboards, dataViewListItems, queryEntityFieldNames, + job, + showTimeRangeSelector: true, + showCustomTimeRangeSelector: false, }; return shallow(); diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index d1634567ff025..177897dacc267 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -26,7 +26,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewListItem } from '@kbn/data-views-plugin/common'; -// import { isDataFrameAnalyticsConfigs } from '../../../../../common/types/data_frame_analytics'; import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils'; import { isValidLabel } from '../../../util/custom_url_utils'; From 6dd3e69f88bd9d72671e3d095afbaa7a0df0d14a Mon Sep 17 00:00:00 2001 From: Melissa Date: Thu, 20 Apr 2023 09:14:43 -0600 Subject: [PATCH 3/5] Remove duplicate translation id --- .../custom_url_editor/custom_time_range_picker.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx index db23af1b6d43a..d0758ba12a4b1 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx @@ -132,7 +132,9 @@ export const CustomTimeRangePicker: FC = ({ onChange={handleStartChange} startDate={start} endDate={end} - aria-label="Start date" + aria-label={i18n.translate('xpack.ml.customUrlsEditor.customTimeRangeStartDate', { + defaultMessage: 'Start date', + })} showTimeSelect /> } @@ -142,12 +144,9 @@ export const CustomTimeRangePicker: FC = ({ onChange={handleEndChange} startDate={start} endDate={end} - aria-label={i18n.translate( - 'xpack.ml.jobsList.editJobFlyout.customUrls.closeEditorAriaLabel', - { - defaultMessage: 'End date', - } - )} + aria-label={i18n.translate('xpack.ml.customUrlsEditor.customTimeRangeEndDate', { + defaultMessage: 'End date', + })} showTimeSelect /> } From 879af4520f4f4082be28f922ac9d39b76876b42c Mon Sep 17 00:00:00 2001 From: Melissa Date: Thu, 20 Apr 2023 17:38:56 -0600 Subject: [PATCH 4/5] only show customTimeRangePicker if dataview has timefield --- .../custom_url_editor/custom_time_range_picker.tsx | 2 +- .../components/custom_urls/custom_url_editor/editor.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx index d0758ba12a4b1..c870e357b22ea 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx @@ -119,7 +119,7 @@ export const CustomTimeRangePicker: FC = ({ label={ } > diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index 3accc9309439b..5c5508b7f55ae 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -86,6 +86,7 @@ export const CustomUrlEditor: FC = ({ job, }) => { const [queryEntityFieldNames, setQueryEntityFieldNames] = useState([]); + const [hasTimefield, setHasTimefield] = useState(false); const { services: { @@ -105,6 +106,9 @@ export const CustomUrlEditor: FC = ({ } catch (e) { dataViewToUse = undefined; } + if (dataViewToUse && dataViewToUse.timeFieldName) { + setHasTimefield(true); + } const dropDownOptions = await getDropDownOptions(isFirst.current, job, dataViewToUse); setQueryEntityFieldNames(dropDownOptions); @@ -356,8 +360,8 @@ export const CustomUrlEditor: FC = ({ /> )} - {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && - showCustomTimeRangeSelector ? ( + {type === URL_TYPE.KIBANA_DASHBOARD || + (type === URL_TYPE.KIBANA_DISCOVER && showCustomTimeRangeSelector && hasTimefield) ? ( Date: Mon, 24 Apr 2023 09:02:44 -0600 Subject: [PATCH 5/5] move custom time range determination to util function --- .../custom_time_range_picker.tsx | 13 +++---- .../custom_urls/custom_url_editor/editor.tsx | 2 +- .../custom_urls/custom_url_editor/utils.ts | 37 ++++++++++--------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx index c870e357b22ea..620aabd1c842b 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx @@ -33,8 +33,7 @@ export const CustomTimeRangePicker: FC = ({ onCustomTimeRangeChange, customTimeRange, }) => { - const [showCustomTimeRangeSelector, setShowCustomTimeRangeSelectorked1] = - useState(false); + const [showCustomTimeRangeSelector, setShowCustomTimeRangeSelector] = useState(false); const { services: { data: { @@ -45,14 +44,12 @@ export const CustomTimeRangePicker: FC = ({ }, } = useMlKibana(); - const onCustomTimeRangeSwitchChange = (e: { - target: { checked: React.SetStateAction }; - }) => { - if (e.target.checked === false) { + const onCustomTimeRangeSwitchChange = (checked: boolean) => { + if (checked === false) { // Clear the custom time range so it isn't persisted onCustomTimeRangeChange(undefined); } - setShowCustomTimeRangeSelectorked1(e.target.checked); + setShowCustomTimeRangeSelector(checked); }; // If the custom time range is not set, default to the timefilter settings @@ -106,7 +103,7 @@ export const CustomTimeRangePicker: FC = ({ /> } checked={showCustomTimeRangeSelector} - onChange={onCustomTimeRangeSwitchChange} + onChange={(e) => onCustomTimeRangeSwitchChange(e.target.checked)} compressed /> diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index 5c5508b7f55ae..523f59c32f224 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -30,7 +30,7 @@ import { DataView } from '@kbn/data-views-plugin/public'; import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils'; import { isValidLabel } from '../../../util/custom_url_utils'; import { type DataFrameAnalyticsConfig } from '../../../../../common/types/data_frame_analytics'; -import { Job } from '../../../../../common/types/anomaly_detection_jobs'; +import { type Job } from '../../../../../common/types/anomaly_detection_jobs'; import { TIME_RANGE_TYPE, TimeRangeType, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts index 777234aa7cb10..f8ee16456e92a 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts @@ -223,6 +223,20 @@ export function buildCustomUrlFromSettings(settings: CustomUrlSettings): Promise } } +function getUrlRangeFromSettings(settings: CustomUrlSettings) { + let customStart; + let customEnd; + + if (settings.customTimeRange && settings.customTimeRange.start && settings.customTimeRange.end) { + customStart = settings.customTimeRange.start.toISOString(); + customEnd = settings.customTimeRange.end.toISOString(); + } + return { + from: customStart ?? '$earliest$', + to: customEnd ?? '$latest$', + }; +} + async function buildDashboardUrlFromSettings(settings: CustomUrlSettings): Promise { // Get the complete list of attributes for the selected dashboard (query, filters). const { dashboardId, queryFieldNames } = settings.kibanaSettings ?? {}; @@ -254,19 +268,14 @@ async function buildDashboardUrlFromSettings(settings: CustomUrlSettings): Promi } const dashboard = getDashboard(); - let customStart; - let customEnd; - if (settings.customTimeRange && settings.customTimeRange.start && settings.customTimeRange.end) { - customStart = settings.customTimeRange.start.toISOString(); - customEnd = settings.customTimeRange.end.toISOString(); - } + const { from, to } = getUrlRangeFromSettings(settings); const location = await dashboard?.locator?.getLocation({ dashboardId, timeRange: { - from: customStart ?? '$earliest$', - to: customEnd ?? '$latest$', + from, + to, mode: 'absolute', }, filters, @@ -308,18 +317,12 @@ function buildDiscoverUrlFromSettings(settings: CustomUrlSettings) { // Add time settings to the global state URL parameter with $earliest$ and // $latest$ tokens which get substituted for times around the time of the // anomaly on which the URL will be run against. - let customStart; - let customEnd; - - if (settings.customTimeRange && settings.customTimeRange.start && settings.customTimeRange.end) { - customStart = settings.customTimeRange.start.toISOString(); - customEnd = settings.customTimeRange.end.toISOString(); - } + const { from, to } = getUrlRangeFromSettings(settings); const _g = rison.encode({ time: { - from: customStart ?? '$earliest$', - to: customEnd ?? '$latest$', + from, + to, mode: 'absolute', }, });