Skip to content

Commit

Permalink
[ML] Add new View job detail flyouts for Anomaly detection and Data F…
Browse files Browse the repository at this point in the history
…rame Analytics (#207141)

## Summary

This PR adds new View job detail flyout for Anomaly detection and Data
Frame Analytics

**For Anomaly detection jobs:** 

- New options are added when clicking on job's name (Remove from page,
View datafeed charts, Navigate to Single Metric Viewer/Anomaly Explorer)

<img width="553" alt="Screenshot 2025-01-24 at 15 02 10"
src="https://github.com/user-attachments/assets/207fa601-b04e-4ab6-b808-e0e420b40584"
/>

- If there's only one job, the remove from {page} is disabled
 
<img width="553" alt="Screenshot 2025-01-24 at 15 02 01"
src="https://github.com/user-attachments/assets/6b2e75a6-e508-4a7d-8e07-dec9b22fc67a"
/>


https://github.com/user-attachments/assets/1a4f0e8f-da15-4e8c-86bd-48045f9144f9




**For Anomaly detection groups:**

- Remove job option is not shown



https://github.com/user-attachments/assets/1976f7dc-8cfe-4f94-975e-233f0225e15b



https://github.com/user-attachments/assets/3381a4f2-ec99-4848-b2fe-9df456306523


**For Data frame analytics jobs:**


https://github.com/user-attachments/assets/7e067ac2-4eda-44b3-bc63-a5901912350f



### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
qn895 and elasticmachine authored Jan 29, 2025
1 parent 3430ab8 commit 201169a
Show file tree
Hide file tree
Showing 23 changed files with 1,162 additions and 188 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { ML_PAGES, type MlPages } from '../../../../locator';
import { FlyoutType } from '../../../jobs/components/job_details_flyout/job_details_flyout_context';
import { ML_APP_LOCATOR } from '../../../../../common/constants/locator';

const ANOMALY_EXPLORER_TITLE = i18n.translate('xpack.ml.anomalyExplorerPageLabel', {
defaultMessage: 'Anomaly Explorer',
});
const SINGLE_METRIC_VIEWER_TITLE = i18n.translate('xpack.ml.singleMetricViewerPageLabel', {
defaultMessage: 'Single Metric Viewer',
});

export const getOptionsForJobSelectorMenuItems = ({
jobId,
page,
onRemoveJobId,
removeJobIdDisabled,
showRemoveJobId,
isSingleMetricViewerDisabled,
closePopover,
globalState,
setActiveFlyout,
setActiveJobId,
navigateToUrl,
share,
}: {
jobId: string;
page: MlPages;
onRemoveJobId: (jobOrGroupId: string[]) => void;
removeJobIdDisabled: boolean;
showRemoveJobId: boolean;
isSingleMetricViewerDisabled: boolean;
closePopover: () => void;
globalState: Record<string, any>;
setActiveFlyout: (flyout: FlyoutType | null) => void;
setActiveJobId: (jobId: string | null) => void;
navigateToUrl: (url: string) => void;
share: SharePluginStart;
}) => {
const pageToNavigateTo =
page === ML_PAGES.SINGLE_METRIC_VIEWER ? ANOMALY_EXPLORER_TITLE : SINGLE_METRIC_VIEWER_TITLE;
const viewInMenu: EuiContextMenuPanelItemDescriptor = {
name: i18n.translate(
'xpack.ml.overview.anomalyDetection.jobContextMenu.viewInSingleMetricViewer',
{
defaultMessage: 'View in {page}',
values: {
page: pageToNavigateTo,
},
}
),
disabled: page === ML_PAGES.ANOMALY_EXPLORER && isSingleMetricViewerDisabled,
icon: 'visLine',
onClick: async () => {
const mlLocator = share.url.locators.get(ML_APP_LOCATOR);
if (!mlLocator) {
return;
}
const singleMetricViewerLink = await mlLocator.getUrl(
{
page:
page === ML_PAGES.SINGLE_METRIC_VIEWER
? ML_PAGES.ANOMALY_EXPLORER
: ML_PAGES.SINGLE_METRIC_VIEWER,
pageState: {
globalState,
refreshInterval: {
display: 'Off',
pause: true,
value: 0,
},
jobIds: [jobId],
query: {
query_string: {
analyze_wildcard: true,
query: '*',
},
},
},
},
{ absolute: true }
);
navigateToUrl(singleMetricViewerLink);

closePopover();
},
};

const items = [
{
name: i18n.translate('xpack.ml.overview.anomalyDetection.jobContextMenu.jobDetails', {
defaultMessage: 'Job details',
}),
icon: 'eye',
onClick: () => {
setActiveJobId(jobId);
setActiveFlyout(FlyoutType.JOB_DETAILS);
closePopover();
},
},
...(showRemoveJobId
? [
{
name: i18n.translate('xpack.ml.overview.anomalyDetection.jobContextMenu.removeJob', {
defaultMessage: 'Remove from {page}',
values: {
page: ANOMALY_EXPLORER_TITLE,
},
}),
disabled: removeJobIdDisabled,
icon: 'minusInCircle',
onClick: () => {
if (onRemoveJobId) {
onRemoveJobId([jobId]);
setActiveJobId(jobId);
setActiveFlyout(null);
}
closePopover();
},
},
]
: []),
{
isSeparator: true,
key: 'separator2',
} as EuiContextMenuPanelItemDescriptor,
...(viewInMenu.disabled ? [] : [viewInMenu]),
{
name: i18n.translate('xpack.ml.overview.anomalyDetection.jobContextMenu.viewDatafeedCounts', {
defaultMessage: 'View datafeed counts',
}),
icon: 'visAreaStacked',
onClick: () => {
setActiveJobId(jobId);
setActiveFlyout(FlyoutType.DATAFEED_CHART);
closePopover();
},
},
];
return items;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* 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 type { EuiContextMenuPanelDescriptor } from '@elastic/eui';
import { EuiContextMenu, EuiNotificationBadge, EuiPopover } from '@elastic/eui';

import React, { useMemo, useState } from 'react';
import { EuiButton } from '@elastic/eui';
import { useUrlState } from '@kbn/ml-url-state';
import { i18n } from '@kbn/i18n';
import type { MlPages } from '../../../../locator';
import { useJobInfoFlyouts } from '../../../jobs/components/job_details_flyout/job_details_flyout_context';
import { getOptionsForJobSelectorMenuItems } from './get_options_for_job_selector_menu';
import { useMlKibana } from '../../../contexts/kibana';

export const GroupSelectorMenu = ({
groupId,
jobIds,
page,
onRemoveJobId,
removeJobIdDisabled,
removeGroupDisabled,
singleMetricViewerDisabledIds = [],
}: {
groupId: string;
jobIds: string[];
page: MlPages;
onRemoveJobId: (jobOrGroupId: string[]) => void;
removeJobIdDisabled: boolean;
removeGroupDisabled: boolean;
singleMetricViewerDisabledIds: string[];
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const { setActiveFlyout, setActiveJobId } = useJobInfoFlyouts();
const {
services: {
share,
application: { navigateToUrl },
},
} = useMlKibana();
const [globalState] = useUrlState('_g');

const closePopover = () => setIsPopoverOpen(false);
const onButtonClick = () => setIsPopoverOpen(true);

const popoverId = `mlAnomalyGroupPopover-${groupId}`;
const button = (
<EuiButton
data-test-subj="mlJobSelectionBadge"
iconType="folderClosed"
iconSide="left"
onClick={onButtonClick}
size="s"
color="text"
>
{groupId}
<EuiNotificationBadge size="s">{jobIds.length}</EuiNotificationBadge>
</EuiButton>
);
const panels: EuiContextMenuPanelDescriptor[] = useMemo(() => {
const items: EuiContextMenuPanelDescriptor[] = [
{
id: 0,
items: [
...jobIds.map((jobId, idx) => ({
panel: jobId,
name: jobId,
})),
{
isSeparator: true,
},
{
name: i18n.translate('xpack.ml.groupSelectorMenu.removeGroupLabel', {
defaultMessage: 'Remove group from {page}',
values: { page },
}),
icon: 'minusInCircle',
disabled: removeGroupDisabled,
onClick: () => {
onRemoveJobId([groupId, ...jobIds]);
closePopover();
},
},
],
},
];

jobIds.forEach((jobId) => {
const options = getOptionsForJobSelectorMenuItems({
jobId,
page,
onRemoveJobId,
removeJobIdDisabled,
showRemoveJobId: false,
isSingleMetricViewerDisabled: singleMetricViewerDisabledIds.includes(jobId),
closePopover,
globalState,
setActiveFlyout,
setActiveJobId,
navigateToUrl,
share,
});
const jobPanel: EuiContextMenuPanelDescriptor = {
id: jobId,
title: jobId,
items: options,
};
items.push(jobPanel);
});
return items;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
jobIds,
setActiveFlyout,
setActiveJobId,
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(globalState),
navigateToUrl,
share,
page,
onRemoveJobId,
removeJobIdDisabled,
removeGroupDisabled,
]);
return (
<EuiPopover
id={popoverId}
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
);
};
Loading

0 comments on commit 201169a

Please sign in to comment.