From d56ca6381e29a00143e7b9396f4eae998734b9a4 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 9 Feb 2021 16:06:30 +0000 Subject: [PATCH 1/3] Use useMlHref hook for ML related links --- .../analyze_in_ml_button.tsx | 108 +++--------------- .../analyze_dataset_in_ml_action.tsx | 43 +++++-- .../top_categories/top_categories_section.tsx | 21 +++- .../sections/anomalies/log_entry_example.tsx | 41 +++++-- 4 files changed, 101 insertions(+), 112 deletions(-) diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index c520243b5b24e..ee33316ecde60 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -7,97 +7,27 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { encode } from 'rison-node'; -import { TimeRange } from '../../../../common/http_api/shared/time_range'; -import { useLinkProps, LinkDescriptor } from '../../../hooks/use_link_props'; +import React, { useCallback } from 'react'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; export const AnalyzeInMlButton: React.FunctionComponent<{ - jobId: string; - partition?: string; - timeRange: TimeRange; -}> = ({ jobId, partition, timeRange }) => { - const linkProps = useLinkProps( - typeof partition === 'string' - ? getEntitySpecificSingleMetricViewerLink(jobId, timeRange, { - 'event.dataset': partition, - }) - : getOverallAnomalyExplorerLinkDescriptor(jobId, timeRange) - ); - const buttonLabel = ( - - ); - return typeof partition === 'string' ? ( - - {buttonLabel} - - ) : ( - - {buttonLabel} + href?: string; +}> = ({ href }) => { + const { + services: { application }, + } = useKibanaContextForPlugin(); + + const handleClick = useCallback(() => { + if (!href) return; + application.navigateToUrl(href); + }, [href, application]); + + return ( + + ); }; - -export const getOverallAnomalyExplorerLinkDescriptor = ( - jobId: string, - timeRange: TimeRange -): LinkDescriptor => { - const { from, to } = convertTimeRangeToParams(timeRange); - - const _g = encode({ - ml: { - jobIds: [jobId], - }, - time: { - from, - to, - }, - }); - - return { - app: 'ml', - pathname: '/explorer', - search: { _g }, - }; -}; - -export const getEntitySpecificSingleMetricViewerLink = ( - jobId: string, - timeRange: TimeRange, - entities: Record -): LinkDescriptor => { - const { from, to } = convertTimeRangeToParams(timeRange); - - const _g = encode({ - ml: { - jobIds: [jobId], - }, - time: { - from, - to, - mode: 'absolute', - }, - }); - - const _a = encode({ - mlTimeSeriesExplorer: { - entities, - }, - }); - - return { - app: 'ml', - pathname: '/timeseriesexplorer', - search: { _g, _a }, - }; -}; - -const convertTimeRangeToParams = (timeRange: TimeRange): { from: string; to: string } => { - return { - from: new Date(timeRange.startTime).toISOString(), - to: new Date(timeRange.endTime).toISOString(), - }; -}; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx index ba3553611c0e6..794097bb7eceb 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx @@ -6,12 +6,13 @@ */ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import React from 'react'; - +import React, { useCallback } from 'react'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; import { TimeRange } from '../../../../../../common/time/time_range'; -import { getEntitySpecificSingleMetricViewerLink } from '../../../../../components/logging/log_analysis_results'; -import { useLinkProps } from '../../../../../hooks/use_link_props'; +import { useMlHref, ML_PAGES } from '../../../../../../../ml/public'; +import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ categorizationJobId: string; @@ -19,12 +20,31 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ dataset: string; timeRange: TimeRange; }> = ({ categorizationJobId, categoryId, dataset, timeRange }) => { - const linkProps = useLinkProps( - getEntitySpecificSingleMetricViewerLink(categorizationJobId, timeRange, { - 'event.dataset': dataset, - mlcategory: `${categoryId}`, - }) - ); + const { + services: { ml, http, application }, + } = useKibanaContextForPlugin(); + + const viewAnomalyInMachineLearningLink = useMlHref(ml, http.basePath.get(), { + page: ML_PAGES.SINGLE_METRIC_VIEWER, + pageState: { + jobIds: [categorizationJobId], + timeRange: { + from: moment(timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + to: moment(timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + mode: 'absolute', + }, + entities: { + [partitionField]: dataset, + mlcategory: `${categoryId}`, + }, + }, + }); + + const mlLinkOnClick = useCallback(() => { + if (!viewAnomalyInMachineLearningLink) return; + + application.navigateToUrl(viewAnomalyInMachineLearningLink); + }, [application, viewAnomalyInMachineLearningLink]); return ( @@ -32,7 +52,8 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ aria-label={analyseCategoryDatasetInMlButtonLabel} iconType="machineLearningApp" data-test-subj="analyzeCategoryDatasetInMlButton" - {...linkProps} + href={viewAnomalyInMachineLearningLink} + onClick={mlLinkOnClick} /> ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx index 1aa6aabf864cc..d24a9dde9cc55 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -6,6 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiTitle } from '@elastic/eui'; +import moment from 'moment'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -18,6 +19,8 @@ import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysi import { DatasetsSelector } from '../../../../../components/logging/log_analysis_results/datasets_selector'; import { TopCategoriesTable } from './top_categories_table'; import { SortOptions, ChangeSortOptions } from '../../use_log_entry_categories_results'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; +import { useMlHref, ML_PAGES } from '../../../../../../../ml/public'; export const TopCategoriesSection: React.FunctionComponent<{ availableDatasets: string[]; @@ -48,6 +51,22 @@ export const TopCategoriesSection: React.FunctionComponent<{ sortOptions, changeSortOptions, }) => { + const { + services: { ml, http, application }, + } = useKibanaContextForPlugin(); + + const analyzeInMlLink = useMlHref(ml, http.basePath.get(), { + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: [jobId], + timeRange: { + from: moment(timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + to: moment(timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + mode: 'absolute', + }, + }, + }); + return ( <> @@ -66,7 +85,7 @@ export const TopCategoriesSection: React.FunctionComponent<{ /> - + diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx index 1a794e6f78c39..36e8f4d6e86a7 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx @@ -9,6 +9,8 @@ import React, { useMemo, useCallback, useState } from 'react'; import moment from 'moment'; import { encode } from 'rison-node'; import { i18n } from '@kbn/i18n'; +import { useMlHref, ML_PAGES } from '../../../../../../../ml/public'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; import { @@ -28,7 +30,6 @@ import { import { useLinkProps } from '../../../../../hooks/use_link_props'; import { TimeRange } from '../../../../../../common/time/time_range'; import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; -import { getEntitySpecificSingleMetricViewerLink } from '../../../../../components/logging/log_analysis_results/analyze_in_ml_button'; import { LogEntryExample, isCategoryAnomaly } from '../../../../../../common/log_analysis'; import { LogColumnConfiguration, @@ -82,6 +83,9 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ timeRange, anomaly, }) => { + const { + services: { ml, http, application }, + } = useKibanaContextForPlugin(); const [isHovered, setIsHovered] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); const openMenu = useCallback(() => setIsMenuOpen(true), []); @@ -114,15 +118,24 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ }, }); - const viewAnomalyInMachineLearningLinkProps = useLinkProps( - getEntitySpecificSingleMetricViewerLink(anomaly.jobId, timeRange, { - [partitionField]: dataset, - ...(isCategoryAnomaly(anomaly) ? { mlcategory: anomaly.categoryId } : {}), - }) - ); + const viewAnomalyInMachineLearningLink = useMlHref(ml, http.basePath.get(), { + page: ML_PAGES.SINGLE_METRIC_VIEWER, + pageState: { + jobIds: [anomaly.jobId], + timeRange: { + from: moment(timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + to: moment(timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + mode: 'absolute', + }, + entities: { + [partitionField]: dataset, + ...(isCategoryAnomaly(anomaly) ? { mlcategory: anomaly.categoryId } : {}), + }, + }, + }); const menuItems = useMemo(() => { - if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLinkProps.onClick) { + if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLink) { return undefined; } @@ -140,11 +153,17 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ }, { label: VIEW_ANOMALY_IN_ML_LABEL, - onClick: viewAnomalyInMachineLearningLinkProps.onClick, - href: viewAnomalyInMachineLearningLinkProps.href, + onClick: () => application.navigateToUrl(viewAnomalyInMachineLearningLink), + href: viewAnomalyInMachineLearningLink, }, ]; - }, [id, openLogEntryFlyout, viewInStreamLinkProps, viewAnomalyInMachineLearningLinkProps]); + }, [ + id, + openLogEntryFlyout, + viewInStreamLinkProps, + viewAnomalyInMachineLearningLink, + application, + ]); return ( Date: Wed, 10 Feb 2021 13:18:32 +0000 Subject: [PATCH 2/3] Tweak naming / add useCallback for handler --- .../top_categories/analyze_dataset_in_ml_action.tsx | 5 ++--- .../sections/top_categories/top_categories_section.tsx | 2 +- .../sections/anomalies/log_entry_example.tsx | 9 +++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx index 794097bb7eceb..af2ff747a8f63 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx @@ -40,9 +40,8 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ }, }); - const mlLinkOnClick = useCallback(() => { + const handleClick = useCallback(() => { if (!viewAnomalyInMachineLearningLink) return; - application.navigateToUrl(viewAnomalyInMachineLearningLink); }, [application, viewAnomalyInMachineLearningLink]); @@ -53,7 +52,7 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ iconType="machineLearningApp" data-test-subj="analyzeCategoryDatasetInMlButton" href={viewAnomalyInMachineLearningLink} - onClick={mlLinkOnClick} + onClick={handleClick} /> ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx index d24a9dde9cc55..f5b94bce74e67 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -52,7 +52,7 @@ export const TopCategoriesSection: React.FunctionComponent<{ changeSortOptions, }) => { const { - services: { ml, http, application }, + services: { ml, http }, } = useKibanaContextForPlugin(); const analyzeInMlLink = useMlHref(ml, http.basePath.get(), { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx index 36e8f4d6e86a7..310a160e7f267 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx @@ -134,6 +134,11 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ }, }); + const handleMlLinkClick = useCallback(() => { + if (!viewAnomalyInMachineLearningLink) return; + application.navigateToUrl(viewAnomalyInMachineLearningLink); + }, [viewAnomalyInMachineLearningLink, application]); + const menuItems = useMemo(() => { if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLink) { return undefined; @@ -153,7 +158,7 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ }, { label: VIEW_ANOMALY_IN_ML_LABEL, - onClick: () => application.navigateToUrl(viewAnomalyInMachineLearningLink), + onClick: handleMlLinkClick, href: viewAnomalyInMachineLearningLink, }, ]; @@ -162,7 +167,7 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ openLogEntryFlyout, viewInStreamLinkProps, viewAnomalyInMachineLearningLink, - application, + handleMlLinkClick, ]); return ( From 09351d0ff17c731a736b674c534d0d5f5138b5f9 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 11 Feb 2021 16:43:03 +0000 Subject: [PATCH 3/3] Handle middle click --- .../log_analysis_results/analyze_in_ml_button.tsx | 12 ++++++++---- .../plugins/infra/public/hooks/use_link_props.tsx | 7 ++++++- .../top_categories/analyze_dataset_in_ml_action.tsx | 12 ++++++++---- .../sections/anomalies/log_entry_example.tsx | 13 ++++++++----- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index ee33316ecde60..00c6b1f93ef88 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -9,6 +9,7 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback } from 'react'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { shouldHandleLinkEvent } from '../../../hooks/use_link_props'; export const AnalyzeInMlButton: React.FunctionComponent<{ href?: string; @@ -17,10 +18,13 @@ export const AnalyzeInMlButton: React.FunctionComponent<{ services: { application }, } = useKibanaContextForPlugin(); - const handleClick = useCallback(() => { - if (!href) return; - application.navigateToUrl(href); - }, [href, application]); + const handleClick = useCallback( + (e) => { + if (!href || !shouldHandleLinkEvent(e)) return; + application.navigateToUrl(href); + }, + [href, application] + ); return ( diff --git a/x-pack/plugins/infra/public/hooks/use_link_props.tsx b/x-pack/plugins/infra/public/hooks/use_link_props.tsx index 225ed5ae4a191..72a538cd56281 100644 --- a/x-pack/plugins/infra/public/hooks/use_link_props.tsx +++ b/x-pack/plugins/infra/public/hooks/use_link_props.tsx @@ -69,9 +69,10 @@ export const useLinkProps = ( const onClick = useMemo(() => { return (e: React.MouseEvent | React.MouseEvent) => { - if (e.defaultPrevented || isModifiedEvent(e)) { + if (!shouldHandleLinkEvent(e)) { return; } + e.preventDefault(); const navigate = () => { @@ -119,3 +120,7 @@ const validateParams = ({ app, pathname, hash, search }: LinkDescriptor) => { const isModifiedEvent = (event: any) => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); + +export const shouldHandleLinkEvent = ( + e: React.MouseEvent | React.MouseEvent +) => !e.defaultPrevented && !isModifiedEvent(e); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx index af2ff747a8f63..15e27705395bb 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx @@ -13,6 +13,7 @@ import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; import { TimeRange } from '../../../../../../common/time/time_range'; import { useMlHref, ML_PAGES } from '../../../../../../../ml/public'; import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; +import { shouldHandleLinkEvent } from '../../../../../hooks/use_link_props'; export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ categorizationJobId: string; @@ -40,10 +41,13 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{ }, }); - const handleClick = useCallback(() => { - if (!viewAnomalyInMachineLearningLink) return; - application.navigateToUrl(viewAnomalyInMachineLearningLink); - }, [application, viewAnomalyInMachineLearningLink]); + const handleClick = useCallback( + (e) => { + if (!viewAnomalyInMachineLearningLink || !shouldHandleLinkEvent(e)) return; + application.navigateToUrl(viewAnomalyInMachineLearningLink); + }, + [application, viewAnomalyInMachineLearningLink] + ); return ( diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx index 310a160e7f267..4362f412d5a78 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx @@ -27,7 +27,7 @@ import { LogColumnHeadersWrapper, LogColumnHeader, } from '../../../../../components/logging/log_text_stream/column_headers'; -import { useLinkProps } from '../../../../../hooks/use_link_props'; +import { useLinkProps, shouldHandleLinkEvent } from '../../../../../hooks/use_link_props'; import { TimeRange } from '../../../../../../common/time/time_range'; import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; import { LogEntryExample, isCategoryAnomaly } from '../../../../../../common/log_analysis'; @@ -134,10 +134,13 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ }, }); - const handleMlLinkClick = useCallback(() => { - if (!viewAnomalyInMachineLearningLink) return; - application.navigateToUrl(viewAnomalyInMachineLearningLink); - }, [viewAnomalyInMachineLearningLink, application]); + const handleMlLinkClick = useCallback( + (e) => { + if (!viewAnomalyInMachineLearningLink || !shouldHandleLinkEvent(e)) return; + application.navigateToUrl(viewAnomalyInMachineLearningLink); + }, + [viewAnomalyInMachineLearningLink, application] + ); const menuItems = useMemo(() => { if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLink) {