Skip to content

Commit

Permalink
[Logs UI] Use useMlHref hook for ML links (#90935) (#91395)
Browse files Browse the repository at this point in the history
* Use useMlHref hook for ML related links
  • Loading branch information
Kerry350 authored Feb 15, 2021
1 parent 60a4895 commit f5bad32
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,97 +7,31 @@

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';
import { shouldHandleLinkEvent } from '../../../hooks/use_link_props';

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 = (
<FormattedMessage
id="xpack.infra.logs.analysis.analyzeInMlButtonLabel"
defaultMessage="Analyze in ML"
/>
href?: string;
}> = ({ href }) => {
const {
services: { application },
} = useKibanaContextForPlugin();

const handleClick = useCallback(
(e) => {
if (!href || !shouldHandleLinkEvent(e)) return;
application.navigateToUrl(href);
},
[href, application]
);
return typeof partition === 'string' ? (
<EuiButton fill={false} size="s" {...linkProps}>
{buttonLabel}
</EuiButton>
) : (
<EuiButton fill={true} size="s" {...linkProps}>
{buttonLabel}

return (
<EuiButton fill={false} size="s" onClick={handleClick}>
<FormattedMessage
id="xpack.infra.logs.analysis.analyzeInMlButtonLabel"
defaultMessage="Analyze in ML"
/>
</EuiButton>
);
};

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<string, string>
): 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(),
};
};
7 changes: 6 additions & 1 deletion x-pack/plugins/infra/public/hooks/use_link_props.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ export const useLinkProps = (

const onClick = useMemo(() => {
return (e: React.MouseEvent | React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => {
if (e.defaultPrevented || isModifiedEvent(e)) {
if (!shouldHandleLinkEvent(e)) {
return;
}

e.preventDefault();

const navigate = () => {
Expand Down Expand Up @@ -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<HTMLAnchorElement | HTMLButtonElement>
) => !e.defaultPrevented && !isModifiedEvent(e);
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,47 @@
*/

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';
import { shouldHandleLinkEvent } from '../../../../../hooks/use_link_props';

export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{
categorizationJobId: string;
categoryId: number;
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 handleClick = useCallback(
(e) => {
if (!viewAnomalyInMachineLearningLink || !shouldHandleLinkEvent(e)) return;
application.navigateToUrl(viewAnomalyInMachineLearningLink);
},
[application, viewAnomalyInMachineLearningLink]
);

return (
Expand All @@ -32,7 +55,8 @@ export const AnalyzeCategoryDatasetInMlAction: React.FunctionComponent<{
aria-label={analyseCategoryDatasetInMlButtonLabel}
iconType="machineLearningApp"
data-test-subj="analyzeCategoryDatasetInMlButton"
{...linkProps}
href={viewAnomalyInMachineLearningLink}
onClick={handleClick}
/>
</EuiToolTip>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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[];
Expand Down Expand Up @@ -48,6 +51,22 @@ export const TopCategoriesSection: React.FunctionComponent<{
sortOptions,
changeSortOptions,
}) => {
const {
services: { ml, http },
} = 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 (
<>
<EuiFlexGroup alignItems="center" gutterSize="s">
Expand All @@ -66,7 +85,7 @@ export const TopCategoriesSection: React.FunctionComponent<{
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AnalyzeInMlButton jobId={jobId} timeRange={timeRange} />
<AnalyzeInMlButton href={analyzeInMlLink} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,10 +27,9 @@ 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 { getEntitySpecificSingleMetricViewerLink } from '../../../../../components/logging/log_analysis_results/analyze_in_ml_button';
import { LogEntryExample, isCategoryAnomaly } from '../../../../../../common/log_analysis';
import {
LogColumnConfiguration,
Expand Down Expand Up @@ -82,6 +83,9 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
timeRange,
anomaly,
}) => {
const {
services: { ml, http, application },
} = useKibanaContextForPlugin();
const [isHovered, setIsHovered] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const openMenu = useCallback(() => setIsMenuOpen(true), []);
Expand Down Expand Up @@ -114,15 +118,32 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
},
});

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 handleMlLinkClick = useCallback(
(e) => {
if (!viewAnomalyInMachineLearningLink || !shouldHandleLinkEvent(e)) return;
application.navigateToUrl(viewAnomalyInMachineLearningLink);
},
[viewAnomalyInMachineLearningLink, application]
);

const menuItems = useMemo(() => {
if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLinkProps.onClick) {
if (!viewInStreamLinkProps.onClick || !viewAnomalyInMachineLearningLink) {
return undefined;
}

Expand All @@ -140,11 +161,17 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
},
{
label: VIEW_ANOMALY_IN_ML_LABEL,
onClick: viewAnomalyInMachineLearningLinkProps.onClick,
href: viewAnomalyInMachineLearningLinkProps.href,
onClick: handleMlLinkClick,
href: viewAnomalyInMachineLearningLink,
},
];
}, [id, openLogEntryFlyout, viewInStreamLinkProps, viewAnomalyInMachineLearningLinkProps]);
}, [
id,
openLogEntryFlyout,
viewInStreamLinkProps,
viewAnomalyInMachineLearningLink,
handleMlLinkClick,
]);

return (
<LogEntryRowWrapper
Expand Down

0 comments on commit f5bad32

Please sign in to comment.